volatile语义
1. 内存可见性
一个线程修改了volatile修饰的变量的值后,新值对另一个线程立即可见
2. 禁止指令重排序优化
对volatile修饰对变量进行读写操作的前后插入内存屏障以达到禁止volatile修饰的变量和其前后其他变量的指令重排序
3. 使用时的表现:
对volatile修饰的变量进行读操作时,将线程工作内存里的变量副本置为无效,然后从主内存读取并刷新工作内存的变量副本
对volatile修饰对变量进行写操作时,将线程工作内存里对变量副本刷新到主内存
4. 注意事项:
volatile只能保证可见性和对此变量单独操作的原子性,当对volatile修饰的变量涉及到复合操作时不能保证原子性,也就不能保证并发的安全性了
比如:
private static volatile int count = 0;
count++; //累加操作是一个复合操作,即使volatile修饰,多线程时也不能保证原子性
private static volatile boolean flag = false;
flag = true; //当作标志位使用时,使用volatile可以保证多线程并发安全性
参考文章【编程玄学】一个困扰我122天的技术问题,我好像知道答案了,不使用volatile时由于JIT优化,会出现很多表面看起来莫名其妙的问题,所以一定要正确使用volatile,不要依赖于编译器和JVM的优化操作。
举例说明
环境:
1 | [qiaojian@Mac ~ ]% java -version [0] |
如下代码片段,注意两个地方:
- 设置flag为true之前sleep 2毫秒
- 在while循环里累加计数
1 | public class TestVolatile { |
编译运行代码执行结果如下:
1 | [qiaojian@Mac ~ ]% java TestVolatile [0] |
然后我们把sleep时间设置长一点比如10毫秒
1 | public class TestVolatile { |
再次编译运行看看效果,发现此时主线程死循环了未退出程序
1 | [qiaojian@Mac ~ ]% java TestVolatile [0] |
再次执行,这次加上禁止JIT优化选项,发现主线程可以正常退出了
1 | [qiaojian@Mac ~ ]% java -Xint TestVolatile [0] |
由此可见,sleep时间的长短对共享变量的可见性也有影响,为什么呢?
因为sleep时间短,while循环短不会触发JIT优化操作。while循环过长超过一定次数时会触发JIT优化操作,将
1 | while (!nonVolatile.flag) { |
优化为:
1 | if (!nonVolatile.flag) { |
然后我们将flag变量使用volatile修饰,会发现不管设置flag为true之前sleep多久,主线程都可以正常退出。这就是volatile关键字对程序正确性所作出的保证。
1 | public class TestVolatile { |
1 | [qiaojian@Mac ~ ]% java TestVolatile [0] |