双重检查机制被破解的声明

在单线程下获取单例的都代码如下所示:

// Single threaded version
class Foo { 
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) 
        helper = new Helper();
    return helper;
    }
  // other functions and members...
  }

但在多线程模式下,上述代码会出现错误,于是出现了下面所示的代码:

// Correct multithreaded version
class Foo { 
  private Helper helper = null;
  public synchronized Helper getHelper() {
    if (helper == null) 
        helper = new Helper();
    return helper;
    }
  // other functions and members...
  }

上面的代码每次调用getHelper()是都会进行同步,双重检查机制则试图在变量helper被分配时避免同步。

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo { 
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) 
      synchronized(this) { //1
        if (helper == null) //2
          helper = new Helper(); //3
      }    
    return helper;
    }
    // other functions and members...
}

但是上述代码并不能在多线程环境下正确的实现单例,最主要的原因是对上述语句helper=new Helper();,Help对象的初始化和对helper变量赋值的顺序会错乱,这是由于java内存模型导致的。

假设上述代码执行一下时间序列:

1、线程 1 进入 getHelper() 方法。
2、由于 helper为 null,线程 1 在 //1 处进入 synchronized 块。
3、线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。
4、线程 1 被线程 2 预占。
5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Helper对象。
6、线程 2 被线程 1 预占。
7、线程 1 通过运行 Helper 对象的构造函数并将引用返回给它,来完成对该对象的初始化。

为展示此事件的发生情况,假设代码行helper=new Helper(); 执行了下列伪代码:
mem = allocate();             //为单例对象分配内存空间.
instance = mem;               //注意,instance 引用现在是非空,但还未初始化
ctorSingleton(instance);    //为单例对象通过instance调用构造函数

还有一种利用ThreadLocal来修复双重检查机制的方法:

class Foo {
	 /** If perThreadInstance.get() returns a non-null value, this thread
		has done synchronization needed to see initialization
		of helper */
         private final ThreadLocal perThreadInstance = new ThreadLocal();
         private Helper helper = null;
         public Helper getHelper() {
             if (perThreadInstance.get() == null) createHelper();
             return helper;
         }
         private final void createHelper() {
             synchronized(this) {
                 if (helper == null)
                     helper = new Helper();
             }
	     // Any non-null value would do as the argument here
             perThreadInstance.set(perThreadInstance);
         }
	}
这个问题在 J2SE 5.0 中已经被修复,可以使用 volatile 关键字来保证多线程下的单例,但是在之前的版本是无效的,需要注意。

// Works with acquire/release semantics for volatile
// Broken under current semantics for volatile
class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null)
                    helper = new Helper();
                }
        }
        return helper;
    }
}
但是 最推荐的完美单例方法是:

public class Something {
    private Something() {}

    private static class LazyHolder {
        private static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

参考文章:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

http://www.iteye.com/topic/652440

http://blog.csdn.net/dl88250/article/details/5439024

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值