JAVA并发-Java内存模型角度分析双重检验锁(Double Check Lock)

单例模式中的懒汉模式大家都很熟悉,我们看一下下面的代码是否有问题:

public class Singleton {
   private static Singleton singleton;
   
   private Singleton(){	}
   
   public static Singleton getInstance(){
       if(singleton == null){
           singleton = new Singleton();
       }
       return singleton;
   }
}

上面的写法是错误的,问题出在它无法保证线程的安全性,我们将其改写一下:

public class Singleton {
   private static Singleton singleton;
   
   private Singleton(){}
   
   public static synchronized Singleton getInstance(){
       if(singleton == null){
           singleton = new Singleton();
       }
       return singleton;
   }
}

可以看到我们在getInstance()方法上加上了synchronized关键字,使其成为了同步方法,但是这样写程序效率就下降了,那有没有可以优化的方法呢? 聪明的小伙伴想到了双重检查DCL,再次进行改写:

public class Singleton {
   private static Singleton singleton;

   private Singleton(){}

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

这样修改之后看似没有问题了,但却是有隐患的,那么问题出在哪里呢?在分析问题之前我们先回顾一下创建对象的过程,实例化一个对象大致分为三个过程:

①. 分配内存空间
②. 初始化对象
③. 将内存空间的地址赋值给对应的引用

但是有些编译期或者处理器为了提升处理效率,会对指令进行重排序,步骤②、③的顺序可能会发生改变,变成如下顺序:

①. 分配内存空间
②. 将内存空间的地址赋值给对应的引用
③. 初始化对象

如果②、③发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return的singleton对象是一个没有被初始化的对象,如下图所示:
在这里插入图片描述
当线程A先于线程B获取Singleton类锁,并且执行到步骤4,即singleton分配了内存地址,而线程B 刚好执行到1,此时if(singleton == null)的条件是不成立的,这时候B取到的对象就是初始化未完成的对象。针对这个问题有如下两种解决办法:

1.禁止初始化阶段步骤② 、③发生重排序。
2.允许初始化阶段步骤2 、3发生重排序,但是不允许其他线程“看到”这个重排序。

一、基于volatile的解决方案:

volatile关键字有禁止指令重拍序的内存语义,所有的写(write)操作都将发生在读(read)操作之前。因此我们可以把singleton变量声明为volatile类型的。如下:

public class Singleton {
   //使用volatile关键字确保该变量对所有线程的可见性,所有对其的写操作先于对其读操作
   private volatile static Singleton singleton;
   
   private Singleton(){}

   public static Singleton getInstance(){
       if(singleton == null){
           synchronized (Singleton.class){
               if(singleton == null){
                   singleton = new Singleton();
               }
           }
       }
       return singleton;
   }
}
二、基于类初始化的解决方案:

利用classloder的机制来确保只有一个线程初始化instance。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。实质上就是不允许其他线程可见。

public class Singleton {
   private static class SingletonHolder{
       public static Singleton singleton = new Singleton();
   }

   public static Singleton getInstance(){
       return SingletonHolder.singleton;
   }
}

参考http://cmsblogs.com/?p=2161

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值