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

14-10-15 banq
                   

该文主要将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;
  }
}
<p>

这段代码其实在多线程环境并没有安全发布。所谓安全发布需要以下几个约束:

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;
  }
}
<p>

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

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

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

这段代码直到第一次调用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;
    }
  }
}
<p>

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

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;
  }
}
<p>

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

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

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

                   

1