Java并发编程——DCL问题
单例模式中的DCL问题我们都知道在程序执行过程中,java虚拟机为了速率,有可能会产生重排序。拿最普通的初始化一个实例来讲。他的过程如下:(1)分配内存;(2)初始化实例;(3)将实例指向该内存。但是由于重排序的特性,可能最终的执行方式是1->3->2。如此就会产生,还没有将实例中的变量初始化完毕,就已经分配了内存。此时该实例已经不为null,但是其中的成员变量,还没有初始化为指定值。
单例模式中的DCL问题
我们都知道在程序执行过程中,java虚拟机为了速率,有可能会产生重排序。拿最普通的初始化一个实例来讲。他的过程如下:
(1)分配内存;
(2)初始化实例;
(3)将实例指向该内存。
但是由于重排序的特性,可能最终的执行方式是1->3->2。如此就会产生,还没有将实例中的变量初始化完毕,就已经分配了内存。此时该实例已经不为null,但是其中的成员变量,还没有初始化为指定值。当别的线程调用时,就会返回错误的结果。
举个栗子,写一个单例模式中的双检查模式。
public class Singleton implements Serializable {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 两层判空,第一层是为了避免不必要的同步
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {// 第二层是为了在null的情况下创建实例//
instance = new Singleton();
}
}
}
return instance;
}
}
但是当并发量高时,可能会出现上面那种情况。
即线程A初始化实例对象时发生重排序;先将实例指向了内存地址,所以这个时候instance不再为null,但是这个时候他还没完全实例化结束。
此时线程B也调用了getInstance方法,检查到instance不为null,于是直接返回。此时返回的就是一个没有初始化值的实例,是一个不完整的对象。造成数据缺失。
这就是单例模式中的DCL问题。
解决
既然知道如何触发DCL问题,那么就可以找到相应的解决方案。
volatile
因此我们可以直接使用共享变量volatile来修饰该实例的引用。
对于volatile修饰的变量来说,禁止重排序,保障运行过程中写操作在读操作之前,如此就可以保障(1)–>(2)–>(3)的顺序,所以也不会产生上述情况了。
private static volatile SingleClass instance;
静态内部类
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不初始化instance,因此不占内存。
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
更多推荐
所有评论(0)