单例设计设计模式 双重检查锁定

  • 当程序中某个类只需要存在一个对象实例时,构造方法私有化,提供对应的取得对象的静态方法。
  • 或者需要采用延迟初始化来降低初始化类和创建对象的开销,只有在使用这些对象时才进行初始化。

比如,下面是非线程安全的延迟初始化对象的示例代码。

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

假设A线程执行代码1的同时,B线程正准备执行代码2。线程A看到的instance为null,那么就会再次进行初始化。

解决方案一:同步处理

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

由于对getInstance()方法做了同步处理,synchronized将导致性能开销。如果方法被多个线程频繁的调用该方法获取,性能将会下降。

解决方案二:双重检查锁定
下面是使用双重检查锁定来实现延迟初始化的示例代码。

public static Instance getInstance() {         // 1
        if (instance == null) {                          // 2:第一次检查
            synchronized (DoubleCheckedLocking.class) {  // 3:加锁
                if (instance == null)                    // 4:第二次检查
                    instance = new Instance();           // 5:问题的根源出在这里
            }                                            // 6
        }                                                // 7
        return instance;                        // 8
    }                                           
  • 第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作。可以大幅降低synchronized带来的性能开销
  • 多个线程试图在同一时间创建对象时,会通过加锁来保证只有一个线程能创建对象。
  • 这里说一下为什么需要第二次检查,第一次检查null时可能有多个线程都检测到instancce为null准备竞争锁。第二次判断就可以解决线程在竞争到锁后再去初始化。
  • 在对象创建好之后,执行getInstance()方法将不需要获取锁,直接返回已创建好的对象。

但是这段代码有问题

在线程执行到第2行,判断instance是否为null时,instance引用的对象有可能还没有完成初始,只是正在初始化,分配了内存空间,instance指向了内存空间,那么这个时候instance就不会为null;但是 instance还未初始化好。
所以返回的instance还是未初始化好的对象

问题的根源
第五行代码:instance = new Instance();一共需要进行以下三个步骤
// 1:分配对象的内存空间
// 2:初始化对象
// 3:设置instance指向刚分配的内存地址
伪代码中的2和3之间,可能会指令会被重排序。也就造成了分配了内存空间,instance指向了内存空间,那么这个时候instance就不会为null;但是 instance还未初始化好

解决重排序这个问题:不允许2和3重排序。
把instance声明为volatile型

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

当声明对象的引用为volatile后,伪代码中的2和3之间的重排序,在多线程环境中将会被禁止重排序。当第一个操作为普通读写时,第二个操作写volatile变量读写时会禁止操作之间的重排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值