1.双重检查锁定 Double check locked
双重检查锁定经常出现在一些框架源码中,目的是为了延迟初始化变量。创建单例模式的创建也可以用到。
2.错误的延迟初始化例子
public class SingleMode2 {
/**
* 先不进行初始化
*/
private static SingleMode2 instance = null;
/**
* 私有构造方法
*/
private SingleMode2() {
}
/**
* 获取实例的时候进行初始化
*
* @return
*/
public static SingleMode2 getInstance() {
if (instance != null) {
instance = new SingleMode2();
}
return instance;
}
这个例子在单线程环境下是可以正常运行的,但是在多线程环境下就有可能会创建多个实例,违反了原有单例的原本意义。我们就需要synchronized。这样该单列模式就是在多线程环境就是安全的。但是这么做就会导致每次调用该方法获取与释放锁,开销很大。
所以这个时候double check就可以让变量真正需要初始化的时候进行加锁。
所有改后的代码:
public static SingleMode3 getInstance() {
if (instance != null) {
synchronized (SingleMode3.class) {
//double check
if (instance == null) {
instance = new SingleMode3();
}
}
}
return instance;
}
double check这个方案缩小锁的范围,减少锁的开销,看起来很完美。但是这个方案 有一些问题却被忽略了。
3.new实例背后的指令
这个被忽略的问题在于 SingleMode3 instance = new SingleMode3();这行代码不是原子指令,使用的是java -c指令,可以快速查看字节码。
// 创建 SingleMode3 对象实例,分配内存
0: new #5 // class com/query/instance
// 复制栈顶地址,并再将其压入栈顶
3: dup
// 调用构造器方法,初始化 SingleMode3 对象
4: invokespecial #6 // Method "<init>":()V
// 存入局部方法变量表
7: astore_1
从字节码可以看到创建一个对象实例,可以分为三步:
1.分配对象内存 2.调用构造方法,执行初始化 3.将对象引用赋值给变量
执行情况:1–>2–>3 或1–>3–>2
虚拟机实际运行时,以上指令可能发生重排序。以上步骤2,3可能发生重排序,但是并不会重排序1的执行顺序,因为2,3指令都依托1指令的执行结果。
Java语言规定了线程执行程序时需要遵循intra-thread semantics(内部线程语义)。内部线程语义保证了重排序不会改变单线程内的执行结果。这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能。
虽然重排序不影响单线程的执行结果,但是在多线程的环境就带来一些问题
线程1 | 线程2 | |
---|---|---|
t1 | 分配内存 | |
t2 | 变量赋值 | |
t3 | 判断对象是否为null | |
t4 | 由于对象不为null,访问该对象 | |
t5 | 初始化对象 |
上面错误的双重检查锁定的示例代码中,如果线程1获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程1执行到t3时刻,线程2刚好进入,由于此时对象已经不为null了,所以线程2可以访问该对象。然后对象还未初始化,所以线程2访问时将会发生异常。
4.volatile作用
正确的双重检查锁定模式需要使用volatile。volatile主要包含两个功能:
-
保证线程可见性
使用volatile定义的变量,将会保证对所有线程的可见性
-
禁止指令重排序
注意,volatile禁止指令重排序在 JDK 5 之后才被修复
使用了volatile的单例模式
/**
* volatile 可以产生内存屏障,防止指令重排序 保证执行步骤 1.new SingleMode3()产生一个地址值 2.把地址值赋给instance 3.初始化对象
*/
private static volatile SingleMode3 instance = null;
/**
* 私有构造方法
*/
private SingleMode3() {
}
/**
* 获取实例的时候进行初始化
*
* @return
*/
public static SingleMode3 getInstance() {
if (instance != null) {
synchronized (SingleMode3.class) {
//double check
if (instance == null) {
//不是原子操作
instance = new SingleMode3();
}
}
}
return instance;
}
}
5.总结
对象的创建可能发生指令的重排序,使用 volatile 可以禁止指令的重排序,保证多线程环境内的系统安全。