并发编程之volatile
- 2019 年 12 月 31 日
- 筆記
文章目录
并发编程之volatile
- volatile称之为轻量级锁,保证了可见性和原子性。
- volatile不会引起上下文切换,因此是轻量级的
volatile的作用
- 保障可见性,有序性和
Long
,double
类型变量读写操作的原子性 volatile
仅仅能保证对其修饰的变量的写操作以及读操作本身的原子性,而这并不表示volatile变量的赋值操作一定具有原子性,例如,如下对volatile
修饰的变量count的赋值操作并不是原子操作:count++
count++
可以分为如下步骤- 读取count的值
count+1
- 将
count+1
的值赋值给count
- 如果
count
是一个共享变量,那个该赋值操作实际上是一个read-modify-write
操作。其执行过程中其他线程可能已经更新了count
的值,因此该操作不具备不可分割性,也就不是原子操作。如果变量count是一个局部变量,那么该赋值操作就是一个原子操作。
- 一般而言如果对volatile修饰的赋值操作,其右边的表达式中只要设计共享变量(包括被volatile修饰的变量本身),那么这个赋值操作就不是原子操作,此时就需要结合
锁
来保证原子性了
原理
保证可见性原理
- 对volatile修饰的变量的读操作之前插入一个
加载屏障
,能够刷新处理器缓存,使其读取的读取到的变量都是线程更新后的最新值。 - 对volatile修饰的变量的写操作(修改)之后插入一个
存储屏障
,能够冲刷处理器缓存,保证后续的线程读取到的值是最新的。
保证有序性原理
- 结合释放屏障和获取屏障保证了有序性
总结
- volatile的写操作相当于锁的释放的效果,java虚拟机会在该操作之前插入一个释放屏障,并在该操作只有插入一个存储屏障
- 释放屏障禁止了volatile写操作与该操作之前的任何读写操作进行重排序,从而保证了volatile写操作之前的任何读写操作会先于volatile写操作之前提交,即其他线程看到写线程对volatile变量的更新时,写线程在更新volatile变量之前执行的内存操作的结果对于度鲜橙必然是可见的。即是保障了有序性
- 存储屏障保证了在写操作之后的更新能够冲刷处理器缓存,使得后续的读线程能够获取最新的值
- volatile的读操作相当于获取锁的效果,Java虚拟机会在该操作之前插入一个加载屏障,并在该操作之后插入一个获取屏障
- 加载屏障用于刷新处理器缓存区,保证读取到volatile修饰变量的最新值,保证可见性
- 获取屏障禁止volatile读操作之后的任何读写操作与volatile读操作进行重排序。因此保证了有序性
volatile变量的开销
- volatile变量的读写不会导致上下文切换,因此开销比锁小
- 读取volatile变量每次都需要从高速缓存或者主内存中读取,而无法暂存在寄存器中,因此可能比读取普通变量的成本要高
使用场景
- 使用volatile变量作为状态标志。比如
volatile int flag=false
,其他线程会读取该状态作为执行某一个操作的依据 - 单例模式下的双重校验锁的实现效果,其中必须使用
volatile
,否则并不能保证对象
可见性