double-check
这样子是只能在单线程中运行:
// Single-threaded version
class Foo {
private static Helper helper;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
多线程中,当然是不行的了。
全部加个锁是可以的
// Correct but possibly expensive multithreaded version
class Foo {
private Helper helper;
public synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
但是会很慢,只有一开始需要锁,当已经构造完成并且将reference赋值给了helper后,就不需要再synchronize了。
部分加个锁
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper;
public Helper getHelper() {
if (helper == null) {
synchronized (this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
// other functions and members...
}
这就是典型的错误了,这里
helper = new Helper();
这句话,本意是创建一个Helper实例,并且将这个实例的引用赋值给helper变量。有可能会被JIT(也就是JVM的优化器)优化。然后构造函数变成内联函数。如果对于构造函数的引用被内联化,那么共享变量有可能会在内存分配的时候就更新,优先于构造函数开始构造这个实例。也就是说:这里的将地址值赋值给helper变量,可能会优先于Helper实例的构造。也就是被reorder了。
加两个锁
// (Still) Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
Helper h;
synchronized(this) {
h = helper;
if (h == null)
synchronized (this) {
h = new Helper();
} // release inner synchronization lock
helper = h;
}
}
return helper;
}
// other functions and members...
}
这样子也是不行的,因为
monitorexit(也就是释放锁)
的规则,只保证了释放锁之前的操作一定要被执行,没有说要被完成。也就导致还是可能会使helper指向的对象时一个未完全构造的对象。
加个volatile
// Works with acquire/release semantics for volatile in Java 1.5 and later
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper;
public Helper getHelper() {
Helper localRef = helper;
if (localRef == null) {
synchronized (this) {
localRef = helper;
if (localRef == null) {
helper = localRef = new Helper();
}
}
}
return localRef;
}
// other functions and members...
}
那么我们添加了volatile变量,就阻止了这个reorder
volatile变量保证,any actions before the write of volatile, must before the write of volatile。也就是说本来在我们volatile变量的写操作之前的操作,被优化时,不会跑到volatile变量的写操作后面。对于这个问题,就是说:对Helper实例的构造
,不会跑到将地址值赋值到helper变量
之前