您的位置:首页 > 编程语言 > Java开发

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

2017-02-17 15:57 260 查看
在单线程下获取单例的都代码如下所示:

// 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息