volatile
修饰符volatile告诉编译器,由volatile修饰的变量可以被程序的其他部分随意修改。在多线程程序中,有时两个或多个线程共享相同的变量,出于效率方面的考虑,每个线程自身可以保存这种共享变量的私有副本,真正的(或主)变量副本在各个时间被更新,例如当进入同步方法时。虽然这种方式可以工作,但是有时效率不高。在有些情况下,重要的是变量的主副本总是反映自身的当前状态。为了确保这一点,简单地将变量修改为volatile,这回告诉编译器必须总是使用volatile变量的主副本(或者,至少总是保持所有私有版本和最新的主副本一直,反之亦然)。此外,访问主变量的顺序必须和所有私有副本相同,以精确地顺序执行。
概括说法:
- 可见性:让一个线程对共享变量的修改,能够及时的被其他线程看到
- 禁止缓存:volatile变量的访问控制符会加个ACC_VOLATILE,不会被CPU告诉缓存区缓存
- 关闭优化:CPU指令重排不会对volatile修饰的变量进行代码优化
问题分析1
1 | public class LearnVolatile1 { |
对于上述代码,无论我们程序运行多少遍,我们都无法把i
的值打印出来,这原因并不是Java的Bug。
我们的代码中,由于isRunning
没有被volatile
修饰,因此是非可见的,所以CPU指令重排的时候会对关于这变量的使用代码进行优化,大致如下
1 | public void run() { |
通过代码可以看到,while
的判断条件变动了,所以我们在3秒后把isRunning
改成false
,循环也没有退出。
要预防这种因为多个线程公用一个变量因为可见性导致代码被CPU指令重排导致的问题,只需要给变量加个volatile
修饰即可。