这是我的单身人士课程。
静态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建立正确的发生关系之前没有办法使这段代码工作?
答案 0 :(得分:1)
我不确定是否真的可能会对同一个变量的读取进行重新排序,但是可以保证局部变量不受其他线程活动的影响。即使没有发生这样的读取重新排序,这种保证也与您在读取时可能同时更新的每个变量相关:如果您读取一个值并将其存储到局部变量中,您可以确定局部变量的值是事后不会突然改变。当然,如果值是引用,则该保证不适用于引用对象的字段。
相关句子可以在JLS §17.4.1:
中找到局部变量(第14.4节),形式方法参数(第8.4.1节)和异常处理程序参数(第14.20节)永远不会在线程之间共享,并且不受内存模型的影响。
所以答案是否定的,不允许编译器撤消引入局部变量的解决方法。
答案 1 :(得分:0)
进行延迟初始化单例的最安全的方法是使用另一个类来保存单个实例字段并依赖Java语言为类初始化提供的保证
public class Singleton {
private static class Holder {
static final Singleton instance = new Singleton();
}
public Singleton getInstance() {
return Holder.instance;
}
}
Holder
类只会在第一次调用getInstance()
时进行初始化(从而创建实例)。
答案 2 :(得分:-1)
我认为你从一开始就没有问题。
您使用synchronized(Singleton.class)
。在synchronized
Java保证任何读/写之前,此关键字很容易反映到内存中以获取所涉及的变量。由于您的Singleton instance
也在类级别声明,因此对其进行的任何修改都可以从其他类中看到,并填充到主内存中。