- 问题原因
- 解决方案
我们在开发过程中,为了限制资源的访问,常常使用单例模式,为了防止单例模式下大对象的开销,可以使用延迟加载的方式在第一次获取对象时来初始化他。
看一下下面的代码
public class UnsafeLazyInitialization {
private static Instance instance;
public static Instance getInstance() {
if (instance == null)
instance = new Instance();
return instance;
}
}
在单线程的情况下这种方式是一个很好的选择,但是再多线程情况下,实例为空的判断和初始化实例这部分临界区存在线程安全的问题
对于多线程的模式下为了保证线程安全下可以使用 java synchronized关键字,保证竞态资源的访问。
public class SafeLazyInitialization {
private static Instance instance;
public synchronized static Instance getInstance() {
if (instance == null)
instance = new Instance();
return instance;
}
}
上述的代中对 getInstance 加锁,防止线程不安全问题,在 instance 对象访问量较少的情况下是一个很好的选择,但是如果 instance 已经初始化完成,此时就没有必要使用锁,增加了不必要的开销。
public class DoubleCheckedLocking {
private static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance();
}
}
return instance;
}
}
利用双重检查锁定来降低锁的开销,看起来似乎很完美,但是这是一个错误的优化,问题就出现在下面这一行代码中
代码读 取到instance不为null时,instance引用的对象有可能还没有完成初始化。
instance = new Instance();问题原因
创建一个对象 instance = new Instance(); 可以分为下面三个步骤 1.开辟内存空间 memory = allocate(); // A 2.对象初始化 ctorInstance(memory); // B 3.设置instance指向刚分配的内存地址 instance = memory; //C
这里的 A、B、C 可能存在重排序的问题 ,以为 A happens-before B 、A happens-before C ,但是 B、C 没有依赖关系,所以初始化对象的执行顺序可能是 A-B-C 或者 A-C-B, 当执行顺序为 A-C-B 时 就存在 当 instance 不为 null 时,instance 没有初始化完成。
解决方案- 不允许出现重排序
volatile 修饰 instance 禁止重排序
public class SafeDoubleCheckedLocking {
private volatile static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance();
}
}
return instance;
}
}
- 重排序对其他线程不可见
利用类加载机制进行初始化
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance ; // 这里将导致InstanceHolder类被初始化
}
}



