问题
DCL(双重检验锁)式单例模式,加了sychronized
关键字后,为何还需要加volatile
关键字?
关键点
指令重排
单例源码
/**
* Created by lerry on 2017/9/21.
* @author lerry
*/
public class SingletonDCL {
private volatile static SingletonDCL singleton;
private static int counter = 0;
private SingletonDCL() {
counter++;
System.out.println(String.format("构造对象被调用[%d]次", counter));
}
/**
* 双重校验锁式(也有人把双重校验锁式和懒汉式归为一类)分别在代码锁前后进行判空校验,
* 避免了多个有机会进入临界区的线程都创建对象,
* 同时也避免了后来线程在"lazythreadsafe"中,先来线程创建对象后,但仍未退出临界区的情况下等待
* @return
*/
public static SingletonDCL getInstance() {
// 模拟同步方法的耗时 start
try {
System.out.println(String.format("[%s]获取对象实例等待1秒", Thread.currentThread().getName()));
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟同步方法的耗时 end
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new SingletonDCL();
}// end if
}// end syn
}// end if
return singleton;
}
}
解释验证
我们创建一个简单的类T
,里面有一个私有成员变量orderCount
,默认值为100
。
编译成.class
之后,使用jclasslib
查看main
方法。
图:T
字节码
new #2 <com/hua/jvm/class01/T>
会为T
分配内存空间。这时会为成员变量附加初始值,为0
。
invokespecial #3 <com/hua/jvm/class01/T.<init>>
会对T
的成员变量进行初始化,把值附为100
。
astore_1
把100
指向t
。
按顺序执行,是没有问题的。
但是,当发生指令重排,CPU乱序执行时,可能会先执行第4行代码,把0
附加给t
。这时,内存空间已分配,单例对象不再是null
,有新的线程来访问时,就可以访问到这个对象, 然后把订单数量0
给拿走了。 这时CPU再执行第3行代码,给成员变量附100
的值。发生了线程安全问题。
相当于在对象半初始化时,有新线程拿到了半初始化的对象,发生线程安全问题。
而添加了volatile
之后,可以保证可见性
和有序性
,避免了此类的线程安全问题。