关于静态初始化程序是线程安全的并且在类加载时由单个线程执行,如果“私有静态字段”使我理解相同的线程安全,我为什么要在单例中使用“私有最终静态字段”?可见性保证 - 假设静态初始化后不会触及静态字段。
简单地说,我确实说下面的两个例子在多线程环境中表现相同:任何时候任何客户端都会返回相同的实例 - 甚至没有最微弱的差异 - 你同意吗?请以任何一种方式告诉我。
示例静态字段:
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
}
最终静态字段示例:
public class Singleton_final {
private final static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
}
亲切的问候, 赫尔曼
答案 0 :(得分:1)
如果是您提供的代码,则没有任何区别,因为您没有实际设置Singleton
课程新实例的方法,因为您没有在课堂外可见的构造函数或setter。
但是,如果没有final
可以更改Singleton
实例,则无法说明。如果您没有final
限定符,理论上可以使用Reflection API来更改实例。我在理论上说,因为我自己从来没有尝试过,而且我无法想到一个偏离脑海的方法。我对任何对Reflection API有更多经验的人都感兴趣
答案 1 :(得分:1)
与许多多线程问题一样,它有点微妙。请允许我稍微深入研究一下这个问题,然后提供一个有点答案。
如果客户看到非null
引用,他们将只看到您创建的Singleton
。其中毫无疑问。以下是两个问题:
null
?Singleton
?后一个问题与您的案例不太相关,因为Singleton
没有任何州。但如果它确实有状态,并且该状态存储在非final
字段中,那么这将是一个问题。例如,给定:
public class Singleton {
private /* non-final */ String name;
private Singleton(String name) {
this.name = name;
}
}
...部分构造的Singleton
是名称为null
的{{1}}(尽管在施工时已设置为非-null
值。)
你想要的可能是(a)没有人看到null
和(b)没有人看到部分构造的对象。要实现这一点,您需要该类没有数据竞争。要做那个,你需要一个先发生过的关系。
所以问题是:初始化instance
的线程与之后读取它的线程之间是否存在先发生关系?
JLS对此有点不确定。在JLS 12.4.2有一个类初始化的详细描述,其中包括锁定类,从而引入一个先发生的边缘。但是JLS中没有任何内容可以指定当一个类已经初始化时会发生什么!在这些情况下,JLS不需要任何锁定,因此不会建立任何先发生关系。严格阅读JLS会表明其他线程中的客户端可能看到null
引用或部分构造的对象。
JLS暗示这不应该发生in 12.4.1:
意图是类或接口类型具有一组初始化器,使其处于一致状态,并且该状态是其他类观察到的第一个状态。
嗯,这很好,这是“意图”,但没有任何东西(除了意图声明)要求它。
final
字段(静态或非静态)获取特殊线程安全语义(JLS 17.5,或在SO上看this question),它基本上提供前面提到的发生前边缘,因此删除数据竞争并确保对完全构造的对象进行非null
引用。