【Java多线程】volatile

概要

volatile 是 JMM(Java Memory Model)的一种机制,只能修饰变量。

一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:

  1. 保证了不同线程对其进行操作时的可见性(即一个线程修改了此变量的值,新值对其他线程来说是立即可见的)。
  2. 禁止进行指令重排序,一定程度上保证了有序性。

但 volatile 不保证原子性。

1. 内存可见性

当线程对一个声明为volatile的共享变量进行写操作后,JMM保证:

  1. 立刻将该线程工作内存中的变量值,立刻强制刷新到主内存中去。
  2. 使其他线程工作内存中该变量的缓存行无效。

注意:当CPU去工作内存中的缓存取值时,发现缓存失效,会重新从主存中取缓存。但是,如果CPU在缓存失效前已经取到了值,那么,即使后来缓存失效,CPU也不会重新取值。

实例分析:volatile为什么不能保证多线程对共享变量的自增操作结果正确?

自增操作不是原子操作,它在线程内的部分为3步:(1)从工作内存缓存的共享变量取值;(2)(CPU的运算核心)进行计算;(3)将计算结果写入到工作内存缓存的变量中。

注意:上述自增操作的第(1)和(3)步,不是主内存和工作内存之间的操作,而是工作内存和CPU核心之间的操作。

而volatile的可见性,只能保证共享变量在工作内存中的值是最新的,而不能保证CPU运算核心中的值是最新的(原因就是上面的“注意”)。这就是很多文章没有写清楚,从而导致基础不好的读者感到困惑,并有了下面错误思考的点。

错误思考:当线程B更改了变量值,线程A即使已经进行过缓存,volatile关键字也保证了,在计算时,总会获得最新值,从而结果没有错误。

这种错误思考的根源就是模糊了工作内存和运算核心的界限,忽略了工作内存与CPU核心计算操作之间,仍有一步取值的操作。而认为CPU运算核心进行计算时,是基于工作内存中的值进行的,且自增操作的读取、写入目标都是主存。

正确的思考:线程A已经将变量的值读取到CPU核心中,甚至已经完成了计算,但未将结果写入线程的工作内存中。此时线程B更改了变量的值,线程A的工作内存可见,但CPU核心中基于旧值的计算结果,最终在自增操作的第(3)步,将仍会直接覆盖线程B的计算结果。

2. 有序性

指令重排序:JVM 在编译 Java 代码时或 CPU 在执行 JVM 字节码时,对现有指令顺序进行重新排序,优化程序的运行效率。遵守以下规则:

  1. 不会对存在数据依赖关系的操作进行重排序。
  2. 不会改变单线程下程序的执行结果。(但是在多线程模式下,可能会影响结果)

volatile禁止指令重排序的规则如下:

  1. volatile写,不能和前面任何指令,进行重排序。
  2. volatile读,不能和后面任何指令,进行重排序。
  3. volatile写,不能和后面的volatile读,进行重排序。

volatile禁止指令重排序的原理:在生成汇编代码时,插入lock前缀指令(不是内存屏障,但类似于内存屏障)来禁止特定类型的指令重排序。

内存屏障是一种屏障指令,会提供 3 个功能,其中2个为可见性中volatile的特性,第3个功能为:确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。即在执行到内存屏障这句指令时,在它前面的操作已经全部完成,后面的操作还未开始。

和 synchronized 的比较

  1. volatile 的本质是告诉JVM当前变量在工作内存中的值是不确定的,需要从主存中读取;而synchronized 的本质则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile 仅能使用在变量级别;synchronized则可以使用在变量、方法、类的级别。
  3. volatile 仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
  6. 如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

适用场景

对于volatile关键字,当且仅当共享变量满足以下所有条件时可使用:

  1. 对共享变量的写入操作不依赖该变量的当前值,或者能确保只有单个线程更新该变量的值。
  2. 共享变量没有包含在具有其他变量的不变式中。
posted @ 2020-12-30 15:46  BWSHOOTER  阅读(99)  评论(0)    收藏  举报