java双重检查锁定

    在Java程序中,有候可能需要推一些高开象初始化操作,并且只有在使用行初始化 。这称为延迟初始化或懒加载

    看一个不安全的延迟初始化:

class UnsafeLazyInitialization {
    private static Instance instance;
    public static Instance getInstance() {
        if (instance == null) // 1:A线程执行
            instance = new Instance(); // 2:B线程执行
        return instance;
    }
}

    A线程执行1后,发现对象instance为null,准备对其new,而B线程却先new了,这造成了错误

  我们可以利用同步锁,保证正确:

 class SafeLazyInitialization {
    private static Instance instance;
    public synchronized static Instance getInstance() {
        if (instance == null)
            instance = new Instance();
        return instance;
    }
}

  但是对整个方法进行同步开销太大,人们想出了双重检查锁定:

 class DoubleCheckedLocking { // 1
    private static Instance instance; // 2
    public static Instance getInstance() { // 3
        if (instance == null) { // 4:第一次检查
            synchronized (DoubleCheckedLocking.class) { // 5:加锁
                if (instance == null) // 6:第二次检查
                    instance = new Instance(); // 7:问题的根源出在这里
            } // 8
        } // 9
        return instance; // 10
    } // 11
}

   最小范围所用同步锁,利用双重检查看似实现了目的,但这出现了一个问题:当A线程4执行时,线程B的7还未执行完成,而线程A判定instance != null.   线程B的7还未执行完成,为什么会出现这种情况?

   看一下new Instance()的底层关键实现:

memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址

   其实是先执行1分配内存,然后再初始化对象和设置instance.然后这里存在重排,2和3的顺序可能被调换:

memory = allocate(); // 1:分配对象的内存空间
instance = memory; // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象

所以当B还执行完7时,A在4判定instance对象已经完成初始化了,如果在ctorInstance(memory)之前去调用instance就会出错。

  解决办法有两个:

1.将instance对象声明为volatile,它会禁止2,3的重排

 class SafeDoubleCheckedLocking {
    private volatile static Instance instance;
    public static Instance getInstance() {
        if (instance == null) {
            synchronized (SafeDoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance(); // instance为volatile,现在没问题了
            }
        }r
        eturn instance;
    }
}
 2.利用基于初始化的解决方案 :JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在

执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化

 class InstanceFactory {
    private static class InstanceHolder {
        public static Instance instance = new Instance();   //因为加锁,保证了可见性
    }
    public static Instance getInstance() {
        return InstanceHolder.instance ; // 这里将导致InstanceHolder类被初始化
    }
}

        

        我们会发现基于类初始化的方案的实现代码更简洁。但基于volatile的双重检查锁定的方案有一个额外的优势:除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。字段延迟初始化降低了初始化类或创建实例的开 销,但增加了访问被延迟初始化的字段的开销。在大多数时候,正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;如果确实需要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。

    

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值