问题
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之后,可以保证可见性和有序性,避免了此类的线程安全问题。
参考
版权声明:本文为limenghua9112原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。