【Java并发编程】volatile(二):可见性问题产生原因,及 volatile 实现原理

volatile 关键字的核心知识点,要关系到 Java 内存模型(JMM,Java Memory Model)上。虽然 JMM 只是 Java 虚拟机这个进程级虚拟机里的一个内存模型,但是这个内存模型,和计算机组成里的 CPU、高速缓存和主内存组合在一起的硬件体系非常相似。理解了 JMM, 可以让你很容易理解计算机组成里 CPU、高速缓存和主内存之间的关系。

1.为什么会出现可见性问题?

事实上,我们可以把 Java 内存模型和计算机组成里的 CPU 结构对照起来看。

我们现在用的 Intel CPU,通常都是多核的的。每一个 CPU 核里面,都有独立属于自己的 L1、L2 的 Cache,然后再有多个 CPU 核共用的 L3 的 Cache、主内存。

因为 CPU Cache 的访问速度要比主内存快很多,而在 CPU Cache 里面,L1/L2 的 Cache 也要比 L3 的 Cache 快。所以,CPU 始终都是尽可能地从 CPU Cache 中去获取数据,而不是每一次都要从主内存里面去读取数据。

在这里插入图片描述

这个层级结构,就好像我们在 Java 内存模型里面,每一个线程都有属于自己的线程栈。线程在读取 COUNTER 的数据的时候,其实是从本地的线程栈的 Cache 副本里面读取数据, 而不是从主内存里面读取数据。

2.JMM 实现 volatile:内存屏障

PS:关于 JMM 可以参考这篇文章…JMM 的结构与计算机硬件很相似(虚拟机内存-内存、线程工作空间-高速缓存、线程-CPU),但是更重要的一点,JMM 的必要性在于规范工作空间的数据与内存数据的交互(可见性、有序性)!!

  • 可见性:线程只能操作自己工作空间中的数据 => volatile 关键字
  • 有序性:为了提高执行效率,会有编译重排序和指令重排序,所以程序中的顺序不一定就是执行的顺序
    • 线程内:as-if-seria,单线程中重排序后不影响执行结果
    • 多线程:happens-before 规则

在 JMM 中,其实是通过内存屏障实现了 volatile,在保证可见性的同时,又保证了有序性

1)内存屏障作用

  • 阻止屏障两侧指令重排序
  • 强制把写修改的数据写回主存(Store)+ 其余缓存中相应的数据失效(Load)=>别的线程再读时需要到主存中重新读取

PS:来看一下保证有序性的 happens-before 规则:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程任意后续操作
  • start()规则:如果线程A执行操作threadB.start(),那么A线程中threadB.start()happens-beforeB的任意操作
  • join()规则:如果线程A执行操作thread.join(),那么线程B的任意操作happens-before于A从threadB.join()返回
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
  • 监视器锁规则:对一个锁(synchronized)的解锁,happens-before于随后对这个锁的解锁
  • 传递性:如果A happens-before B,B happens-before C ,那么 A happens-before C

2)内存屏障分类

  • Store:更新主存

  • Load:让高速缓存失效,强行刷新


  • LoadLoad:volatile读之后,避免volatile读操作和后面普通的读操作进行重排序
  • StoreStore:volatile写之前,禁止上面的普通写与后面的volatile写重排序
  • LoadStore:volatile读之后,避免volatile读操作和后面普通的写操作进行重排序
  • StoreLoad:volatile写之后,避免volatile写操作与后面可能存在的volatile读写操作发生重排序
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_33762302/article/details/114297597