在 java 核心卷 1 中对 volatile 关键字是这么描述的:
volatile 关键字为实例域的同步访问提供了一种免锁机制. 如果声明一个域为 volatile, 那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的.
上述解释可以通过下面代码直观的描述:
- public class VolatileAtomicSample {
- static Logger logger = Logger.getLogger(VolatileAtomicSample.class);
- private static boolean initFlag = false;
- public static void refresh() {
- logger.info("refresh data ......");
- initFlag = true;
- logger.info("refresh data success ......");
- }
- public static void loadData() {
- while (!initFlag) {
- }
- logger.info("线程:" + Thread.currentThread().getName()
- + "当前线程嗅探到 initFlag 的状态的改变!");
- }
- public static void main(String[] args) {
- Thread threadA = new Thread(VolatileAtomicSample::loadData,"threadA");
- threadA.start();
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Thread threadB = new Thread(() -> {
- refresh();
- }, "threadB");
- threadB.start();
- }
上面的代码中线程 B 执行了 refresh 方法, 貌似 initFlag 的值被改成了 true, 但是执行的结果仍然是:
refresh data ......
refresh data success ......
并没有打印: 线程: threadA 当前线程嗅探到 initFlag 的状态的改变!
如果将变量 initFlag 用关键字 volatile 修饰, 那么结果就是:
19/12/21 20:58:15 INFO volatile1.VolatileAtomicSample: refresh data ......
19/12/21 20:58:15 INFO volatile1.VolatileAtomicSample: refresh data success ......
19/12/21 20:58:15 INFO volatile1.VolatileAtomicSample: 线程: threadA 当前线程嗅探到 initFlag 的状态的改变!
那么 volatile 的工作原理是什么, 在底层是如何执行的.
可以通过一个图来描述:
主内存简称 "主存" , 工作内存可以叫做线程的缓存.
线程 A 将 initFlag 先 read 出来, 然后 load 到线程 A 的缓存中去, 然后 use 就是交给代码来使用.
线程 B 也按线程 A 的方式来使用 initFlag.
但是线程 A,B 之间是没有可见性的. 所以, 线程 A 读出来的 initFlag=flase, 是在线程 A 的工作内存中操作, 线程 B 读出来的 initFlag 是在线程 B 的工作内存中操作, 也是 initFlag=flase, 就算线程 B 将 initFlag 重新赋值成 true, 线程 A 也不知道, 就出现了第一个结果.
如果将 initFlag 加上一个 volatile 修饰符, 那么线程 B 的中将 initFlag 的值改成 true, 就会执行 assign, 将 true 从代码部分放回线程 B 的工作内存, 然后线程 B 的工作内存回执行 store, 然后再将修改的数据 write 到主存中去, 这个时候, 各个线程回对总线做监听, 这个总线可以理解成一条河, 那么所有线程都从总线中知道 initFlag 的状态发生改变了. 然后每个对 initFlag 操作的工作线程都停止自己的操作, 重新从主存中获取最新的数据再操作.
所以就是下面的这个图:
volatile 的工作原理就是上面的样子.
画外音: 图确实画的有点渣.
来源: http://www.bubuko.com/infodetail-3343802.html