作用
volatile关键字主要有两个功能:
1、变量的可见行
2、防止指令重排序
可见行
如下图:Java线程工作模型
Java的线程内存模型分为主内存和工作内存。
主内存是所有的线程所共享的,工作内存是每个线程自己有一个,不是共享的。
线程工作时将要用到的变量从主内存拷贝到自己的工作内存,然后在工作内存中进行读和写,写完之后,可能没被更新到主内存去。导致其他线程从主内存拷贝数据到自己的工作区时,拷贝的不是最新的数据。这就是内存可见性问题。
而被volatile修饰的变量,在发生改变的时候会告诉其他线程副本,该变量不可用,需要从主内存重新获取,这样就保证了变量的可见行
防止指令重排
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();//yi
}
}
}
return instance;
}
}
instance变量如果不添加volatile修饰的话,会造成指令重排。
这里的指令重排序主要体现在instance = new Singleton()这条语句上了。
这条语句显然是个复合操作,可以简单分下,(已完成类加载 ,假设在堆上分配内存)
1.在堆中分配对象内存
2.填充对象必要信息+具体数据初始化+末位填充
3.将引用指向这个对象的堆内地址
2-3步骤不知道哪个先执行,就会造成指令重排。如果单例中有个变量的话,就会造成变量为空。
解决办法
采用内存屏障的方式来防止质量重排。
硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障
- 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据
- 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见
Java提供了四种内存屏障
- LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕
- StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见
- LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕
- StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
happen-before原则
- Java中happen-before原则,只要是一下八种情况的,都不会出现指令重排
- 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
- 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
- volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
- happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
- 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
- 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
- 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。