8.volatile 关键字


8. volatile

8.0 为什么存在内存可见性问题 -> 缓存一致性问题

可见性是指某线程修改了一个变量的值,新值对于其他线程是立即可见的。

为了提高代码执行速度,CPU 先把资源从内存加载到 CPU 缓存中(L1, L2等),但操作完并并不一定实时写回内存。此外,每条线程有自己的工作内存 Working Memory,用于保存被该线程所使用的变量的主内存的副本,线程对变量的所有操作都在工作内存中进行,不能直接读写主内存中的数据。

volatile 关键字能解决这个问题。

8.1 volatile 功能

  1. 保证变量的内存可见性
    • 当某个线程对 volatile 变量进行写操作时,JMM 会立即把该线程对应的本地内存刷新到主内存中。
    • 当某个线程对 volatile 变量进行读操作时,JMM 会立即把该线程的本地内存置为无效,并从主内存中重新读取。
  2. 禁止 volatile 变量与 普通变量 的指令重排
    • 禁止指令重排是为了实现一种类似锁的线程间通信机制,但比锁更轻量级

虽然 volatile 保证了内存可见,但 java 中的运算操作符不能保证原子性,同样会导致并发的不安全。需要额外使用锁来保证原子性。

8.1.1 如何限制指令重排

通过内存屏障来限制指令重排:

  • 内存屏障分为 读屏障 Load Barrier写屏障 Store Barrier
  • 阻止屏障两侧的指令重排序;
  • 写后强制把写缓冲区/高速缓存中的脏数据写回主内存,读时先让缓存中数据失效

JMM 内存屏障

  • 在每个 volatile 写操作前插入一个 StoreStore 屏障;
  • 在每个 volatile 写操作后插入一个 StoreLoad 屏障;
  • 在每个 volatile 读操作后插入一个 LoadLoad 屏障;
  • 在每个 volatile 读操作后再插入一个 LoadStore 屏障。

具体:

  • LoadLoad 屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore 屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,这个屏障会吧Store1强制刷新到内存,保证Store1的写入操作对其它处理器可见。
  • LoadStore 屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad 屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

8.1.2 volatile 与 锁 的区别

volatile 和 锁 都能保证内存可见性,因此 volatile 可以作为一个轻量级的锁。

  • volatile 仅能保证单个变量的读写具有原子性,锁可以保证整个临界区代码的原子性
  • 锁的功能更强大,但 volatile 的性能更有优势

8.2 volatile 实现原理

从字节码的角度来看,对 volatile 变量进行写操作时,在赋值后,JVM 会多执行一行带有 lock 前缀的指令,该指令会把 CPU 缓存中的新值写回内存。

// lock + 空操作 来执行缓存回写
0x01a3de24: lock addl $0x0, (%esp)

同时,缓存一致性协议保证了各个处理器的缓存是一致且最新的。

  1. lock 前缀的指令会引起处理器缓存写回内存;
  2. 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
  3. 当处理器发现自己的本地缓存失效后,就会从主内存中重读最新值。

文章作者: Yu Yang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yu Yang !
 上一篇
7.指令重排 与 happens-before 7.指令重排 与 happens-before
7. 指令重排 与 happens-before指令重排可以在 CPU 闲置(等待变量装载等)时,先执行其他指令,提高性能。分为: 编译器优化重排:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。 指令并行重排:现代处理
下一篇 
AQS AQS
11. AQSAQS,AbstractQueuedSynchronizer,抽象队列同步器,是一个抽象类。 是用来构建锁和同步器的框架。基于 AQS 实现的锁: ReentrantLock ReentrantReadWriteLock C
  目录