当前位置:   article > 正文

volatile 关键字

volatile 关键字

我们先看这段代码

  1. class MyCounter {
  2. public int flag = 0;
  3. }
  4. public class ThreadDemo15 {
  5. public static void main(String[] args) {
  6. MyCounter myCounter = new MyCounter();
  7. Thread t1 = new Thread(() -> {
  8. while (myCounter.flag == 0) {
  9. // 这个循环体咱们就空着
  10. }
  11. System.out.println("t1 循环结束");
  12. });
  13. Thread t2 = new Thread(() -> {
  14. Scanner scanner = new Scanner(System.in);
  15. System.out.println("请输入一个整数: ");
  16. myCounter.flag = scanner.nextInt();
  17. });
  18. t1.start();
  19. t2.start();
  20. }
  21. }

        上述代码的逻辑是,t1 有个循环,flag 作为判断循环条件的变量,t2 修改这个变量,修改完了 t1 线程按道理也就结束了。

        但运行出来的结果却并没有结束,这个情况叫做“内存可见性问题”,即一个线程针对一个变量进行读取操作,另一个线程针对这个变量进行修改,此时读到的值,不一定是修改后的,因为这个变量没有感知到变量的变化。(编译器 / JVM在多线程环境下优化时产生了误判)

        这里使用汇编来理解大概就是以下两步操作:

1. load,把内存中的 flag 值读到寄存器中

2. cmp,把寄存器中的值和 0 进行比较,然后根据结果决定下一步(条件跳转指令)

        由于 load 执行速度太慢(相对于 cmp ),再加上反复 load 的结果都是一样的,JVM 就做了一个大胆的决定,只进行一次 load。不再真正的重复 load 了,因为判定没人修改 flag 值(编译器优化的一种方式)。

        此时,就需要程序猿手动进行过干预了 → 给 flag 这个变量加上 volatile 关键字,意思是告诉编译器这个变量是“易变的”,这个时候编译器就不会进行这种激进式的优化了。

  1. static class Counter {
  2. public volatile int flag = 0;
  3. }
  4. // 执行效果
  5. // 当用户输入非0值时, t1 线程循环能够立即结束.

从 JMM(Java Memory Model)的角度表述内存可见性问题:

在 Java 程序里,有主内存,每个线程也有自己的工作内存 ( t1 和 t2 的工作内存不是同一个东西 ) t1 线程在进行读取的时候,只是读取了工作内存中的值.                                                                  t2 线程在进行修改的时候,先修改工作内存的值,然后再把工作内存的内容同步到主内存中          但是由于编译器优化,导致 t1 没有重新从主内存中同步数据到工作内存,因此读到的结果就是“修改之前”的。

把“主内存”代替为“内存”,“工作内存”代替为“工作存储区”这样就好理解了。工作内存(工作储存区)并非是所说的内存,而是 CPU 寄存器 + CPU 的 cache(缓存)

volatile 能保证内存可见性

volatile 修饰的变量, 能够保证 "内存可见性"

代码在写入 volatile 修饰的变量的时候

改变线程工作内存中volatile变量副本的值

将改变后的副本的值从工作内存刷新到主内存

代码在读取 volatile 修饰的变量的时候

从主内存中读取volatile变量的最新值到线程的工作内存中

从工作内存中读取volatile变量的副本

前面我们讨论内存可见性时说了, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存), 速度

非常快, 但是可能出现数据不一致的情况.

加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了.

volatile 不保证原子性

        volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性, volatile 保证的是内存可见性。因此如果某个代码既要考虑原子性也要考虑内存可见性,就把 synchronized 和 volatile 都用上即可。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/391350
推荐阅读
相关标签
  

闽ICP备14008679号