Java 非易失性双重检查锁定,是否可能?
这是我的单身班 静态Java 非易失性双重检查锁定,是否可能?,java,volatile,java-memory-model,double-checked-locking,Java,Volatile,Java Memory Model,Double Checked Locking,这是我的单身班 静态实例字段不是易变的,因此会出现重新排序/可见性问题。要解决此问题,请将实例val字段设为最终字段。由于实例是正确构造的,所以如果客户机看到实例,他们应该总是看到val字段初始化 static class Singleton { private static Singleton instance; private final String val; public Singleton() { this.val = "foo"; } pub
实例
字段不是易变的,因此会出现重新排序/可见性问题。要解决此问题,请将实例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建立正确的“发生在之前”关系,就无法使此代码正常工作?执行惰性初始化单例的最安全方法是使用另一个类来保存单实例字段并依赖保证Java语言提供类初始化
public class Singleton {
private static class Holder {
static final Singleton instance = new Singleton();
}
public Singleton getInstance() {
return Holder.instance;
}
}
Holder
类只会在第一次调用getInstance()
时进行初始化(从而创建实例)。我认为您从一开始就没有问题
您可以使用
synchronized(Singleton.class)
。在synchronized
Java保证在这个关键字之前的任何读/写操作都可以很容易地反映到内存中,以用于相关变量。由于您的单例实例
也是在类级别声明的,因此对它的任何修改都很容易从其他类中看到,并填充到主内存中。我不确定是否真的会对同一变量的读取进行重新排序,但可以保证局部变量不受其他线程活动的影响。即使这样的读取重新排序没有发生,这种保证也适用于在读取时可能同时更新的每个变量:如果读取一个值并将其存储到局部变量中,则可以确保局部变量的值不会在之后突然改变。当然,如果该值是引用,则该保证不适用于被引用对象的字段
相关句子可在以下内容中找到:
局部变量(§14.4)、形式方法参数(§8.4.1)和异常处理程序参数(§14.20)从不在线程之间共享,并且不受内存模型的影响
因此答案是否定的,编译器不允许撤销引入局部变量的变通方法。自Java 1.5以来,单例实现的问题已经用枚举解决了:问题其实并不在于单例。而是关于双重检查锁定本身。这些问题难以解决。这就是为什么我们应该使用Java从
Java.util.concurrent
包中提供的抽象结构。问题是如何读取方法第一行和最后一行的值。这些读数在同步块之外,所以在读写之间建立关系之前不会发生任何情况。这就是为什么双重检查锁定在没有volatile关键字的情况下不起作用。因此,我很确定问题是存在的。您的第一行只是一个空检查,实例只初始化了一次;所以如果这条线很容易看到,很好,你很好;如果没有,则进入同步状态,仍然没有问题。但我看不出哪一行是最后一行。据我所知,非易失性读取可以被重新排序。这就是为什么第一次读取可能返回非空值(检查失败),第二次读取可能返回空值。为了说明这一点,我放了第二段代码——它演示了有效的重新排序(至少我认为它是有效的),这会破坏多线程应用程序(第一个线程从实例字段读取空值,然后第二个线程将实例设置为非空值,然后第一个线程从实例字段读取非空值并返回存储在temp中的空值)。可能是我的推理错误,请随意更正。记住两点:1.对于由synchronized()分隔的语句,不会发生重新排序.2.synchronized()后,所有涉及的变量都会刷新到主内存中,并且确保值是最新的。因此,您的第一个null第二个null案例永远不会发生。啊,谢谢,这正是我要找的。