volatile
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。
volatile变量特性
一旦一个共享变量被volatile修饰之后,就具备了两层语义:
- 保证不同线程对这个变量进行操作时的可见性:即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 禁止进行指令重排序。
多线程可见性
CPU为了提高处理性能,并不直接和内存进行通信,而是将内存的数据读取到内部缓存(L1,L2)再进行操作,操作完并不能确定何时写回到内存。
对volatile变量:
- 进行写操作,会将这个变量所在缓存行的数据写回到内存;
- 为了保证各个CPU的缓存一致性,每个CPU通过嗅探在总线上传播的数据来检查自己缓存的数据有效性,当发现自己缓存行对应的内存地址的数据被修改,就会将该缓存行设置成无效状态,当CPU读取该变量时,发现所在的缓存行被设置为无效,就会重新从内存中读取数据到缓存中。
禁止重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
- 重排序操作不会对存在数据依赖关系的操作进行重排序;
- 不管怎么重排序,要保证单线程模式下程序的执行结果不能被改变;
对volatile变量,在编译时会在指令序列中插入内存屏障来禁止特定类型的处理器重排序:
- 当程序执行到volatile变量(无论读还是写操作):其前面操作的更改已经全部进行,且结果对后面的操作可见;且其后面的操作还未开始;
- 在进行指令优化时:不能把volatile变量访问前的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
- 即执行到volatile变量时,其前面的所有语句都已执行完,后面的所有语句都未执行;且前面语句的结果对volatile变量及其后面语句可见。
单例模式
单例模式是一种常见的设计模式,它的核心结构为一个特殊的单例类。通过单例模式可以保证系统中一个类只有一个实例。
Double Check
单例模式中,常会忽略volatile关键字;若没有此关键字,在高并发情形下,出现CPU重排序情形下,就可能会出错:
public class DoubleCheckSingleton {
private static volatile DoubleCheckSingleton _instance = null;
public static DoubleCheckSingleton getInstance() {
if (_instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (_instance == null) {
_instance = new DoubleCheckSingleton();
}
}
}
return _instance;
}
}
在java中一条语句的赋值,在字节码下就会出现多条(汇编码就更多了):
new #3 <concur/DoubleCheckSingleton>
invokespecial #4 <concur/DoubleCheckSingleton.<init>>
putstatic #2 <concur/DoubleCheckSingleton._instance>
特别是第二、三条语句,可能会出现先执行第三条语句,然后执行第二条的情形;此时其他线程就会获取到未初始化的变量。
静态变量方式
通过静态变量方式,可保证单例的获取。通过私有嵌套类,可以保证只在需要时创建实例。
public class StaticSingleton {
private static class SingletonHolder {
static StaticSingleton _instance = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder._instance;
}
}