为何不推荐Java双重检测加锁(DCL)

########### 20190617更新 

DCL缺点

1. 对象init有了内存地址,object != null,但是初始化还没完全,有属性没有被初始化:object.attribute == null,这样调用object.attribute就null point了。(声明为volatile可以解决)

2. 变得复杂(volatile,两次判空),耗时(用了synchronized 影响性能)

那好的实现方式是什么呢?优雅 优雅decent

采用静态内部类,

public class Singleton {
    private Singleton(){}
    
    public static getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton()
    }
}

 

1. 懒加载:静态内部类和外部类没啥关系,所以调用外部类不生成 单例对象,必须主动调用。

2. 一个对象:确保由JVM内部机制 初始化后确保static的字段

3. 没有多线程相关的synchronized、volatile,仅仅右JVM内部实现

完美优雅

############

为何使用DCL

    早起JVM性能上有待优化,延迟初始化可以避免高开销(用时再初始化),或降低程序启动时间。延迟初始化需要使用同步,但同步执行的速度慢。所以就先用 不同步来判断(一次为null,就new,所以之后基本不为null),这样大多数不为null 情况下就不耗时了。等new的时候,要保证原子性,所以再加锁。

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {  //1
      if (instance == null)          //2
        instance = new Singleton();  //3
    }
  }
  return instance;
}

 

问题

1.可见性

    线程安全:原子性、可见性。 保证了原子性,却没有考虑可见性。

    可见性简单说:

    a线程更改对象y----》更改CPU缓存1中的y----》写入内存中的y

    b线程读取对象y----》读取CPU缓存2中的y   (如果CPU缓存2没有从内存中更新y,b线程y看到过时的数据)

 

  1.     instance对象 被a线程 release置空,instance == null,写入内存。
  2.     b线程没从内存更新,所以instance != null,b线程用过时的对象 运行,导致状态不一致。

2.部分初始化

    instance 指向了对象的内存地址(即instance != null),但instance没调用构造函数,所以其属性是失效的状态。

    new对象cpu的指令是多步的

 

  1. 分配内存  
  2. 初始化对象(内存赋值)  
  3. 内存地址赋给instance (instance != null)

   但CPU指令可能被优化重排序,3跑到2前面了。

   假设乱序后

 

  1.     a线程到3后(之后调2),CPU 调度b线程,
  2.     b线程判断 instance != null,调用instance.hello.toString(),
  3.     instance没调用构造函数,hello为null,直接崩溃了。

 

解决:

    将单例对象声明为volatile类型,修复可见性、排序(参看volatile优缺点

 

为何废弃DCL:

    DCL产生的原因(无竞争同步的执行速度很慢,以及JVM启动很慢)已经不复存在,因而不是高效的优化措施。

 

参考

    《Java并发编程实战》286页

 

    

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值