楼子湾

导航

 

1.关键字 volatile是Java虚拟机提供的最轻量级的同步机制

2.当一个变量被定义称volatile之后,它将具备两项特性:

1)保证此变量对所有线程可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程可以立即得知

 volatile变量在各个线程的工作内存中是不存在一致性问题的(从物理存储角度看,各个线程的工作内存中volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),

但是JAVA里面的运算符操作并非原子操作,这导致volatile变量的运算在并发下一样的不安全

由于volatile变量只能保证可见性,在不符合一下两条规则的运算场景中,我们仍然需要通过枷锁(使用synchoronized、java.util.concurrent中的锁或原子类)来保证原子性

  •   运算结果并不依赖变量的当前值,或者能够保证只有单一的线程修改变量的值:这里不依赖指各自独立运算,不以当前变量为基础进行运算;要么单线程运算
  •   变量不需要与其他的状态变量共同参与不变约束

2)禁止指令重排

 

例如:B线程依赖于A线程的配置

Map configOptions;
char[] configText;
// 此变量必须定义为volatile
volatile boolean initialized = false;


// 假设以下代码在线程A中执行
// 模拟读取配置信息,当读取完成后,将initialized设置为true,通知其他线程配置可用
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configtext, configOptions);
initialized = true;

--------------------------------------------------------------------------
// 假设以下代码在线程B中执行
// 等待initialized 为true,代表线程A已经把配置信息初始化完成
while(!initialized) {
      sleep();          

}
// 使用线程A中初始化好的配置信息
doSomethingWithConfig();

如果定义 initialized变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致位于线程A中最后一条代码 “initialized = true;”被提前执行,可能会放在代码块的第一行执行,这样在B线程使用配置信息的代码就可能出现错误 

 

3.针对long和double型变量的特殊规则

  Java内存模型要求lock unlock read load use assingn store write这八中操作都有原子性,但是对于64为的数据类型(long和double),在模型中特别定义了一条宽松的规定:

  允许虚拟机没有被volatile修饰的64位数据的读写操作划分位两次32位的操作来进行,即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这个四个操作的原子性

  如果多线程共享一个并未声明位volatile的long或double类型的变量,并且同时对他们进行读取和修改操作,那么某些线程可能会读取到一个既不是原值,也不是其他线程修改值的代表了“半个变量”的数值。在64位不会出现,但在32位x86平台下的虚拟机存在这种风险(非原子访问的风险)

posted on 2021-02-16 15:15  楼子湾  阅读(163)  评论(0编辑  收藏  举报