Volatile 原理,优化,应用详解

1. volatile 原理

Volatile 是轻量级的synchronized,保证了共享变量的可见性(一个线程修改共享变量,另一个变量能读到这个修改的值。),volatile不会引起线程上下文的切换和调度,所以比synchronized执行成本低。

volatile 修饰的共享变量,进行写操作的时候会多出一行lock开头的汇编指令。Lock前缀的指令做了两件事:
1)将当前处理器的缓存行的数据写回系统内存。(此时处理器会独占内存)
2)同时使其他cpu里缓存该内存地址的数据无效。实现缓存一致性协议:每个处理器利用嗅探技术访问系统内存和其它处理器的内部缓存,当出现数据不一致的时候,就要设置成无效状态(缓存行失效),然后下次访问该处理器缓存行的数据时候,需要重新从内存中读取到处理器缓存中(缓存行填充)。
这里写图片描述
CPU术语的定义
这里写图片描述

2. Volatile 写读的内存语义以及实现
线程A写volatile变量,JMM会把该线程对应的本地内存的共享变量值刷新到主存中。通过主存向接下来要读这个volatile变量的线程发出消息。
线程B读一个volatile变量,JMM会把线程对应的本地内存置为无效,接收之前线程发出的消息,从主存中读取共享变量,这样线程B在读之前的所有写都是可见的。

实现:编译器在生成字节码时,在指令序列中插入内存屏障来禁止处理器指令重排序。
保守插入策略:
1)每个volatile写前插入StoreStore屏障
2)每个volatile写后插入StoreLoad屏障
3)每个volatile读后插入LoadLoad屏障
4)每个volatile读后插入LoadStore屏障
x86处理器只会对写-读操作做重排序,因此,可以省略3种内存屏障,只需要在volatile写后插入一个StoreLoad屏障即可实现volatile内存语义。volatile写的开销大于读的开销。

这里写图片描述

3.Volatile的使用优化

LinkedTransferQueue这个类在使用volatile变量的时候,用追加的字节的方式来优化出队和入队。
LinkedTransferQueue 用一个内部类来定义队列的头结点和尾节点,内部类PaddedAtomicReference追加了15个变量(共60字节),加上父类的value变量,一共64字节。
为什么追加字节可以优化呢?
因为很多处理器的高速缓存行是64字节宽。当字节不足64的时候,处理器会将它们都读到同一个高速缓存行,所以如果两个volatile节点同时写操作就会相互锁住同一个缓存行。
以下可以不用这种方式:
a.当处理器的高速缓存行不是64字节宽;
b.Volatile变量写入没有那么频繁。

4.Volatile在双重检查的单例模式的使用
双重检查的由来。
(1)
这里写图片描述
问题:非线程安全的。造成2重复执行。

(2)
这里写图片描述
问题:synchronized锁住了整个方法。当访问线程数多的时候,性能低。
(3)
这里写图片描述
问题:
Instance=new Instance() 创建对象的时候,可以分成
(1)分配对象内存空间
(2)初始化对象
(3)使instance指向刚分配的内存地址。
这里写图片描述
这时(2)和(3)之间可能会发生重排序。因为在单线程中,(2)(3)重排序也不会影响结果。
而在多线程中,线程1执行new instance()的时候步骤(2)(3)发生重排序。使得instance先指向分配好的内存地址,而没有初始化对象。此时线程2 执行if(instance==null)判断不为空,直接返回instance。
但是这使instance没有初始化,导致返回错误。

(4)
这里写图片描述
这是正确写法。Volatile 保证new instance()的时候步骤(2)(3)不会发生指令重排序,也就是对象先初始化再将instance的引用指向对象内存地址。


参考: java并发编程的艺术

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章