DCL,volatile,Final

Double check lock模式的典型代码:

 

 

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;   
    }
}

 

 

理论上来将,DCL模式能够以优异的性能解决单例模式在多线程下的并发问题,但是实际情况下,因为编译器会采用代码重排(instruction reorder)的优化手段,从而导致偶尔出现返回一个未完全初始化(内存已经分配,reference对象已经创建并且指向创建的内存地址,但是构造函数还没有开始执行或者未完全执行完毕)的Singleton实例并引起程序异常。

 

为了解决这个问题,JDK1.5以及之后的版本,可以通过volatile以及final关键字来修正DCL中的问题:

 

volatile比较简单,只需要在instance变量定义前加上volatile关键字即可,由于使用volatile关键字会禁止JVM的代码重排,因此可以解决DCL中存在的问题。

 

使用final关键字稍微有些地方需要注意,先看代码:

 

 

public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) { 
        this.value = value; 
    }
}
 
public class Foo {
   private FinalWrapper<Helper> helperWrapper = null;
 
   public Helper getHelper() {
      FinalWrapper<Helper> wrapper = helperWrapper;
 
      if (wrapper == null) {
          synchronized(this) {
              if (helperWrapper == null) {
                  helperWrapper = new FinalWrapper<Helper>(new Helper());
              }
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
}

 

首先实例变量value被声明为了final,但是比较特别的一点是在Foo.getHelper()方法中,首先定义了wrapper这个局部变量,并将helperWrapper赋给了它,然后判断wrapper是否为空再进行后续的处理。必须使用wrapper的原因是因为做为局部变量,wrapper不存在线程不安全的问题,如果直接使用helperWrapper,因为之前代码重排的原因,仍然有可能得到一个不完全初始化的对象实例。

 

再回到final关键字的语义,在JDK1.5以及之后的版本中,编译器能够保证被final关键字定义的属性在对象实例化中能够被正确的初始化(非final对象因为代码重排的原因可能不能满足这个条件),因此在最后一段程序中,wrapper.value永远能够被正确的初始化。

 

补充1:DCL是在Singleton模式中最容易被使用的,目的是为了解决Singleton在多线程下的线程安全问题。其实为了解决这个问题,除去上述方法之外,还有一些比较直接的方式,例如直接在声明Singleton instance时就将它初始化(无法做到延时加载)或者在getInstance()方法上加入synchronized关键字(性能问题),比较有趣的是下面这种通过内部类来实现延时加载(原理是利用了内部类直到被实际引用时才会加载的机制):

 

 

class Foo {
    private static class HelperHolder {
       public static Helper helper = new Helper();
    }
 
    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}
 

补充2:关于volatile关键字,volatile变量保证在各个工作线程内存之间的一致性,但是不代表任何情况下基于volatile变量的操作都是线程安全的(在操作依赖于当前变量值并且会修改它的情况下,因此我们仍然需要使用DCL来保证单例的唯一性)。同时volatile变量保证了代码顺序的不变性,但是在和其他非volatile变量一起使用时,仍然会触发编译器的代码重排。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值