volatile关键词在多线程中的应用
volatile 是什么
首先我们就来介绍一下 volatile,它是 Java 中的一个关键字,是一种同步机制。当某个变量是共享变量,且这个变量是被 volatile 修饰的,那么在修改了这个变量的值之后,再读取该变量的值时,可以保证获取到的是修改后的最新的值,而不是过期的值。
// 摘自《Java 并发编程 78 讲》
使用场景
基本属性的赋值
例如int,boolean的赋值,因为赋值操作为原子操作。应要注意到i++ 不是原子操作,不在此列。而布尔值的操作基本上是直接进行赋值的,故《Java 并发编程 78 讲》对使用场景的介绍指“布尔标记位”。这种场景不做深究。
作为触发器
先看一段代码,以及结果:
public class VolatileClass { int beforeVolatile = 0; /** * 作为触发器 */ volatile boolean flag; int afterVolatile = 0; public VolatileClass() { flag = false; } public void init() { beforeVolatile = 1; flag = true; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } afterVolatile = 2; System.out.println(String.format("程序初始化结束:beforeVolatile[%d],flag[%b],afterVolatile[%d]", beforeVolatile, flag, afterVolatile)); } public void afterInit() { while (!flag) { // todo sout << "程序还未完成初始化"; System.out.println(String.format("程序还未完成初始化:beforeVolatile[%d],flag[%b],afterVolatile[%d]", beforeVolatile, flag, afterVolatile)); } System.out.println(String.format("检测程序初始化结束:beforeVolatile[%d],flag[%b],afterVolatile[%d]", beforeVolatile, flag, afterVolatile)); } public static void main(String[] args) { VolatileClass volatileClass = new VolatileClass(); Thread thread = new Thread(new Runnable() { @Override public void run() { volatileClass.afterInit(); } }); thread.start(); Thread init = new Thread(new Runnable() { @Override public void run() { volatileClass.init(); } }); init.start(); try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } } }
结果:
程序还未完成初始化:beforeVolatile[0],flag[false],afterVolatile[0] 检测程序初始化结束:beforeVolatile[1],flag[true],afterVolatile[0] 程序初始化结束:beforeVolatile[1],flag[true],afterVolatile[2] Process finished with exit code 0
可以观察到几个现象:
- 线程thread是先启动的,他一开始进入while时,flag为false,初始化未完成,而当init线程去修改flag为true之后,thread线程立马就跳出了while循环,打印了“检测程序初始化结束”,触发器生效!
- 在“检测程序初始化结束”中打印的beforeVolatile值已经为1,这根据"happens before"规则,是可以保证发生在volatile 修饰的flag之前修改的变量也具有了可见性。
volatile的作用域
虽然基本上用作触发器是用boolean变量,但是其作用于对象、数组。那么其对象的属性、数组的元素发生变化,都会被此关键词保证其可见性,此处可以通过修改flag变量的类型进行实验,已亲测,便不贴代码占文了。
volatile的原理
说一下自己的理解,并不是官方解释。
首先看一下JMM(JAVA内存模型)
每个线程都有他的本地内存,是从共享内存中拷贝出来的,这也是造成线程之间数据不可见性的原因。当被volatile修饰的对象被修改,那么JMM保证它会被立马更新到共享内存中,同时其他线程会从共享内存中读取其值。
同时,个人理解(还未考证),在flush的这个过程,他并不是将volatile修饰的单个对象的修改刷入共享内存,而是将发生修改过的对象全部刷入共享内存,同时refresh过程会将共享内存中全部更新的对象全部载入到本地内存中。这样就形成了happens-before。
另外,volatile关键字会一定程度上禁止编译器在编译阶段优化时一定范围的指令重排序,以保证happens-before规则。有兴趣可以另外自行了解。