Java中安全发布和安全初始化

该文主要将Java的单例和JMM内存管理以及同步锁性能综合起来考虑,对传统Double-Checked Locking 提出了进一步完善,并用Intel芯片和ARM芯片分别进行了测试。从文中看出,在多线程中做好单例值的初始化真不是一件简单的事情,也许因为单例和多线程在概念上本身不匹配吧。

传统的DCL单例代码如下:


public class UnsafeDCLFactory {
private Singleton instance;//问题在这里,应该让其他线程看到instance是否被初始化

public Singleton get() {
if (instance == null) {
// check 1
synchronized (this) {
if (instance == null) {
// check 2
instance = new Singleton();
}
}
}
return instance;
}
}

这段代码其实在多线程环境并没有安全发布。所谓安全发布需要以下几个约束:
1. 使用静态的初始化器进行初始化
2.通过一个适当加锁字段交换引用
3.通过一个volatile或AtomicX字段交换引用
4.使用final标识字段,initialize the value into a final field (JLS 17.5). Caveat emptor:只对之前没有其他人看到过的对象有效,对于已经发布的对象使用final已经太迟了。

对于UnsafeDCLFactory显然有以下问题:
1.并没有使用静态初始化器
2.至少有一个单例读是不受锁保护的
3.单例没有通过volatile 字段发布
4.单例也没有通过final字段发布.

重构如下:


public class SafeDCLFactory {
private volatile Singleton instance;

public Singleton get() {
if (instance == null) { // check 1
synchronized(this) {
if (instance == null) {
// check 2
instance = new Singleton();
}
}
}
return instance;
}
}

该文还提出了使用初始化器发布单例的方式,通常我们使用下面静态初始化器来实现(单例的另外一种实现)


public class HolderFactory {
public static Singleton get() {
return Holder.instance;
}

private static class Holder {
public static final Singleton instance = new Singleton();
}
}

这段代码直到第一次调用get()方法,Holder才被初始化,但是因为是在构造器以外使用了final字段,这太迟了,得用一个wrapper包裹一下,以确保没有人看到过单例:


public class FinalWrapperFactory {
private FinalWrapper wrapper;

public Singleton get() {
FinalWrapper w = wrapper;
if (w == null) { // check 1
synchronized(this) {
w = wrapper;
if (w == null) {
// check2
w = new FinalWrapper(new Singleton());
wrapper = w;
}
}
}
return w.instance;
}

private static class FinalWrapper {
public final Singleton instance;
public FinalWrapper(Singleton instance) {
this.instance = instance;
}
}
}

该文最后还讨论了使用使用加锁字段实现单例的安全发布:


public class SafeLocalDCLFactory implements Factory {
private volatile Singleton instance;

@[author]Override[/author]
public Singleton getInstance() {
Singleton res = instance;
if (res == null) {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
return res;
}
}

关于安全初始化,文章推荐使用final字段,参考All Fields Are Final (banq注:这实际是不变性的值对象)

有关在Intel和ARM芯片上测试参考原文,点击标题可进入。

[该贴被banq于2014-10-15 10:54修改过]