正文
helper = new Helper();
return helper;
}
// other functions and members...
}
这段代码如果运行在多线程环境下,将会出现问题。很显然的一个问题,两个或者多个Helper对象将会被分配内存,其他问题我们会在后面提到,我们先简单的给方法加一个synchronized关键字。
// 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方法的时候都要进行同步,下面的Double-Checked Locking方式避免了当Helper对象被实例化之后再次进行同步:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
不幸的是,这段代码在存在编译优化或多处理器共享内存的情况下不能够正常工作。
二、Double-Checked Locking不能够正常工作
为什么上文说Double-Checked Locking不能够正常工作有很多的原因,我们将会描述一对很显而易见的原因。通过理解存在的问题,我们尝试着去修复Double-Checked Locking存在的问题,然而我们的修复可能并没有用,我们可以一起看看为什么没有用,理解这些原因,我们去尝试着寻找更好的方法,可能还是没有用,因为还是存在一些微妙的原因。
1)第一个不能正常工作的原因
Double-Checked Locking不能够正常工作的一个很显然的原因是对helper属性的写指令和初始化Helper对象的指令可能被冲排序,因此当其他线程再次调用getHelper方法的时候,将会得到一个没有被初始化完成的Helper对象,如果这个线程访问了这个对象没有被初始化的属性,那么就会出现位置错误。
我们来看看对于下面这行代码,在Symantec JIT编译器环境下的指令重排序的例子:
singletons[i].reference = new Singleton();
下面是实际执行的代码:
0206106A mov eax,0F97E78h
0206106F call 01F6B210 ; allocate space for
; Singleton, return result in eax