该文主要将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修改过]