要编写正确的并发程序,关键问题在于:
在访问共享的可变状态时需要进行正确管理。
我们希望:
1.应防止一个线程正在使用对象状态时,而另一个线程在同时修改该状态;——原子性 、互斥性(临界区)
2.应确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。——内存可见性
volatile
稍弱(轻量级)的同步机制,是保证内存可见性的一种手段。
语义
1.当一个变量定义为一个volatile之后,能够保证该变量在线程之间可见性。
当一个线程修改了这个变量的值,新值对于其他线程是立即可知的。而普通变量不能做到这一点,普通变量的值在线程间传递需要通过主内存。例如:线程A修改了普通变量的值,然后向主内存回写,另外一条线程B在线程A回写完成之后才从主内存进行读取操作,新变量值才会对B可见。
2.禁止指令重排序优化。
普通的变量只能保证方法执行过程所有依赖赋值结果的地方能获取到正确的结果,但不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。而volatile修饰的变量,在给其赋值之后,会插入内存屏障,防止前面的指令跑到之前执行。
【备注】:JDK1.5之前不能避免重排序问题,也不能安全地使用DCL实现单例模式。
应用场景
1.对变量的写入操作不依赖变量的当前值,或者确保只有单个线程更新变量的值。
一个变量的自增操作(++)就需要依赖变量当前值,此时仅仅通过volatile是不能保持原子操作的,如果仅是一个赋值操作则可以。
2.在访问变量时不需要加锁
3.该变量不会与其他状态变量一起纳入不变性条件
【备注】:经常把一些状态标记设为volatile类型变量
加锁与volatile
比较内容 | volatile | 加锁(synchronized或Lock等) |
---|---|---|
性能 | 不会使线程阻塞,比synchronized更轻量级;volatile变量与普通变量读操作差不多;但是写操作会慢一些(设置内存屏障防止重排序);大多数情况下总开销更低 | 虚拟机对锁实行了消除和优化,所以volatile不一定比它快多少; |
确保原子性 | 不可以 | 可以 |
确保可见性 | 可以 | 可以 |
机制
Java内存中定义了8种操作以及对volatile的特殊规定
操作 | 作用域 | 用途 |
---|---|---|
lock | 主内存 | 将变量标识为独占状态 |
unlock | 主 | 释放处于锁定状态的变量 |
read | 主 | 从主传输到工作内存 |
load | 工作内存 | 把read的变量放入工作内存的副本 |
use | 工作内存的变量 | 传给执行引擎 |
assign | 工作 | 把执行引擎接收到的值赋给工作内存的副本 |
store | 工作 | 从工作传给主 |
write | 主 | 放入主内存的变量 |
volatile变量会满足如下规则:
1.在工作内存中,每次使用volatile的变量,都必须先从主内存刷新最新的值(用于保证能看见其他线程对变量修改的值),即use,load,read必须连续一起出现。
2.在工作内存中,修改volatile变量的值,必须立刻同步回主内存(保证其他线程能看见自己修改的值),即assign,store,write必须连续一起出现。