标签:lock 创建 关键字 内存屏障 实现 解决方法 字节 完全 过期
当变量被声明为 volatile的时候,在对volatile变量进行写操作时候,汇编指令会插入一个 Lock前缀指令,这个指令会引发两件事情。
缓存一致性协议
在多处理器下,为了保证各个处理器缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了。当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行置为无效状态。
从 jdk5 开始,volatile变量的写-读可以实现线程之间的通信。
从内存语义的角度来看,volatile写-读与锁的释放-获取具有相同的内存效果。volatile写和锁的释放具有相同的内存语义,volatile读与锁的获取有相同的内存语义。
为了实现volatile内存语义,JMM将限制编译器重排序和处理器重排序。JMM针对编译器制定的volatile重排序规则表。
能否重排序 | 第二个操作 | ||
---|---|---|---|
第一个操作 | 普通读写 | volatile读 | volatile写 |
普通读写 | NO | ||
volatile读 | NO | NO | NO |
volatile写 | NO | NO |
总结为一下3点:
为了实现volatile的内存语义,编译器在生成字节码的时候,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
StoreLoad屏障
旧的java内存模型中,虽然不允许volatile变量之间重排序,但是允许 volatile变量与普通变量重排序。
因此在旧的内存模型中,volatile的写-读没有锁的释放-获取所具有的语义。为了实现一种比锁更轻量级的线程之间的通信机制。JSR-133中增强了volatile的内存语义,严格限制编译器和处理器对volatile变量与普通变量的重排序。
public class InstanceFactory {
private static Instance instance;
public static Instance getInstance(){
if(instance==null){
synchronized(InstanceFactory.class){
if(instance==null){
instance =new Instance();
}
}
}
return instance;
}
static class Instance{
//some code
}
}
这个代码在 看上去是正确,有可能会得到一个为完全初始化成功的对象。
创建对象的代码可以分为3个部分:
memory = allocate(); //1.分配内存
ctorInstance(memory);//2.初始化对象
instance = memory; //3.设置instance的内存地址
这个过程可以重排序为:
memory = allocate(); //1.分配内存
instance = memory; //3.设置instance的内存地址
ctorInstance(memory);//2.初始化对象
所以当第二个判断 instance == null ,当判断对象不为空的时候,对对象进行访问可能会访问到一个为完全初始化成功的对象。
解决方法:1.禁止 操作2、3重排序
可以使用volatile变量修饰instance 禁止操作2、3重排序。 这个方案需要在jdk1.5或者更高的版本才行
public class InstanceFactory {
private static volatile Instance instance;
public static Instance getInstance(){
if(instance==null){
synchronized(InstanceFactory.class){
if(instance==null){
instance =new Instance();
}
}
}
return instance;
}
static class Instance{
//some code
}
}
标签:lock 创建 关键字 内存屏障 实现 解决方法 字节 完全 过期
原文地址:https://www.cnblogs.com/joeCqupt/p/9434324.html