很长一段时间中对于volatile关键字都是一知半解的,由于工作中用的比较少,也没有对其深入了解,直到看了《深入理解java虚拟机》之后,才有进一步的了解。
volatile是java虚拟机提供的最轻量级的同步机制,只能作用于变来那个,具备两种特性:
保证此变量对所有线程的可见性:可见性是指一旦一个线程修改了此变量的值,其他线程能立即得知。
禁止指令的重排序(本文暂不涉及)
由于volatile的可见性分析是基于java内存模型的,此处对java内存模型做个简单的概述。
java内存模型
主内存和工作内存
主内存:所有的变量都存储在主内存中
工作内存:每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,不能直接从主内存中读写变量,不同线程之间也无法访问其他线程工作内存中的变量,线程之间变量值的传递只能通过主内存完成。
线程-工作内存-主内存三种之间的关系图
java内存间相互操作(八种内存操作指令)
指令功能: 完成主内存和工作内存之间的交互, 主要功能为实现变量从主内存拷贝到工作内存,如何从工作内存同步会主内存
指令详解:
lock(锁定):作用于主内存中的变量,限制该变量为一个线程独占的状态
unlock(解锁):作用于主内存的变量,将变量从一个线程独占的状态中释放出来,释放后的变量才能被其他变量占用。
read(读取):作用主内存的变量,将一个变量从主内存加载到工作内存,以便之后的load操作使用。
load(载入):作用于工作内存,它把read读取的变量值放到工作内存的变量副本中
use(使用):作用于工作内存,把工作内存中的变量的值传递给执行引擎,每当虚拟机遇到需要使用变量的值的字节码指令是会执行此操作
assign(赋值):作用于工作内存,把从执行引擎中接收到的值赋值给工作内存的变量,每当虚拟机遇到给变量赋值的字节码指令是执行此操作
store(存储):作用于工作内存,把工作内存中的变量传送到主内存中,以便之后的write操作使用。
write(写入):作用于主内存,将store操作中传递过来的值存储到主内存变量中。
如何实现volatile的可见性
声明: T为一个线程 V为volatile变量
- 保证自己所在的线程可见其他线程对变量所作的修改
- 线程T对变量V执行的前一个动作是load的时候,线程T才能对变量V执行use操作,线程T对变量执行的后一个操作是use的时候,线程T才能对变量执行load操作。换句话说,线程T对变量V执行的操作中,read(read和load绑定一起工作), load和use必须绑定在一起。保证了自己所在的线程获取到的变量数据永远是最新的。
- 保证自己所在线程对变量的修改对其他线程是可见的。
- 线程T对变量V执行的前一个动作是assign的时候,线程T才能对变量V执行store操作,线程T对变量V执行的后一个操作是use的时候,线程T才能对变量V执行assign操作,换句话说,线程T对变来那个V执行的操作中,assign, store,write(store和write一起工作)必须绑定在一起工作,保证了自己所在线程的修改对其他线程来说是可见的。
可见性推论:由上述两条规则可知,当执行引擎想要使用某一个volatile变量的时候,他必须从再次从主内存中读取变量刷新工作内存中的变量值。当执行引擎对某个变量执行完某操作并希望将该变量存入工作内存中时,必须同步将该变量的值同步到主内存中。
volatile不具备线程安全性
从以上的分析中可以得出volatile实现了线程之间的可见性,但是volatile并不具备线程安全的特性。
原因:以上的可见性分析只是针对单个线程而言的,但是当两个线程分别对同一个变量V执行read操作,并对读取后的变量执行相关的运算,之后两个线程会将自己操作的变量同步会主内存,这时候就会存在后同步到操作会覆盖前一个同步到操作。
总结:volatile关键字保证了不同线程对变量的修改对于其他线程来说是立即可见的,但volatile并不具备线程同步到特性!
要想保证原子性,可以加上syn关键字或者使用原子变量进行操作
volatile如何实现内存可见性:
深入来说:通过加入内存屏障和禁止重排序优化来实现的。
对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
通俗地讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。
Volatile
关键字volatile的主要作用就是使变量在多个线程间可见。深入来说:通过加入内存屏障和禁止重排序优化来实现的。
1、对volatile变量执行写操作时,会在写操作后加入一条store屏障指令;
2、对volatile变量执行读操作时,会在读操作后加入一条load屏障指令。
Synchronized
Synchronized的功能包括:原子性(同步) 与 可见性。
JMM关于synchronized的两条规定:
1、线程解锁前,必须把共享变量的最新值刷新到主内存中;
2、线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要使用的是同一把锁)。
线程解锁前对共享变量的修改在下次加锁时对其他线程可见。
线程执行synchronized互斥代码的过程
1、获得互斥锁
2、清空工作内存
3、从主内存拷贝变量的最新副本到工作内存
4、执行互斥代码
5、将更改后的共享变量的值刷新到主内存
6、释放互斥锁