2

这是我的单身课程。

静态instance字段不是易失性的,因此会出现重新排序/可见性问题。为了解决它,实例val字段被设置为最终字段。val由于实例是正确构造的,因此如果他们看到实例,它的客户应该总是看到已初始化的字段。

    static class Singleton {
    private static Singleton instance;
    private final String val;
    public Singleton() { this.val = "foo"; }

    public static Singleton getInstance() {
        if (instance == null)
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
            }
        }
        return instance;
    }
    public String toString() { return "Singleton: " + val; }
}

但是还有另一个问题 - 我有两个不受保护的“实例”字段读取,可以(?)重新排序,以便客户端可能得到 null 而不是实际值:

public static Singleton getInstance() {
    Singleton temp = instance;
    if (instance != null) return temp;
    else { /* init singleton and return instance*/ }
}

为了解决这个问题,我觉得我可以引入局部变量:

public static Singleton getInstance() {
    Singleton temp = instance;
    if (temp == null)
        synchronized (Singleton.class) {
            if(instance == null) {
                instance = new Singleton();
                temp = instance;
        }
    }
    return temp;
}

这似乎解决了这个问题,因为只有一个不受保护的值读取,所以不会发生真正的邪恶。但是......我刚刚修改了程序流程,而没有(几乎?)改变它的单线程语义。这是否意味着编译器可以撤消我的解决方法,因为这种转换是安全的,并且如果不与 volatile 建立适当的先发生关系,就无法使此代码工作?

4

3 回答 3

1

我不确定是否真的会重新排序对同一变量的读取,但可以保证局部变量不受其他线程活动的影响。即使没有发生这种读取重新排序,这种保证也适用于在您读取它时可能同时更新的每个变量:如果您读取一个值并将其存储到一个局部变量中,您可以确定局部变量的值确实之后不会突然改变。当然,如果值是引用,则该保证不适用于被引用对象的字段。

相关句子可以在JLS §17.4.1中找到:

局部变量(第 14.4 节)、形式方法参数(第 8.4.1 节)和异常处理程序参数(第 14.20 节)永远不会在线程之间共享,并且不受内存模型的影响。

所以答案是否定的,不允许编译器撤消引入局部变量的解决方法。

于 2014-09-02T18:05:50.977 回答
0

进行惰性初始化单例的最安全方法是使用另一个类来保存单个实例字段并依赖 Java 语言为类初始化提供的保证

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

  public Singleton getInstance() {
    return Holder.instance;
  }
}

该类Holder只会在第一次getInstance()被调用时被初始化(并因此创建实例)。

于 2014-08-22T09:17:33.190 回答
-1

我不认为你从一开始就有问题。

你用synchronized(Singleton.class). Java 保证在synchronized此关键字之前的任何读/写都可以很容易地反映到所涉及变量的内存中。由于您Singleton instance也是在类级别声明的,因此对它的任何修改都可以从其他类中轻松看到并填充到主内存中。

于 2014-08-22T09:44:03.900 回答