我喜欢Java中渴望的单例的简单性,并且有关它的大多数文章都将其创建线程称为安全的。
class Singleton {
public static final Singleton instance = new Singleton ();
private Singleton (){};
public static Singleton getInstance(){
return instance;
}
}
但是,我听说有人声称它的创建可能毕竟不是线程安全的。例如,一个消息来源声称,如果使用多个类加载器或App域,那是不安全的。
JVM是否保证Eager Singleton的创建是线程安全的,例如,两个线程不会意外地同时创建Singleton?
编辑: 创建对象的线程安全时是否需要关键字final?如果该字段不是final,它不是线程吗?
答案 0 :(得分:8)
您使用的方法是线程安全的。由于您尚未引用您正在谈论的声明,因此我无法直接解决它们。 但是Java语言规范对此主题很明确。
在section 17.5中描述
final
字段还允许程序员实现线程安全的不可变的 没有同步的对象。线程安全的不可变对象是 被所有线程视为不可变的,即使数据争用用于传递 引用线程之间的不可变对象。这可以提供 安全保证,防止因错误或不正确使用不可变类 恶意代码。 final字段必须正确使用才能提供 保证不变性。当一个对象被认为是完全初始化的 构造函数完成。只能看到引用的线程 该对象已完全初始化后的对象 查看该对象最终的正确初始化的值 字段。
答案 1 :(得分:2)
我不认为“热心单身”这个名字是合理的。
考虑JLS §12.4.1., When Initialization Occurs
<块引用>类或接口 T 将在以下任何一项第一次出现之前立即初始化:
T
是一个类,并创建了 T
的实例。static
声明的 T
方法。static
声明的 T
字段。static
声明的 T
字段,并且该字段不是常量变量 (§4.12.4)。因此,初始化以及 Singleton
的实例化将在第一次调用方法 getInstance()
时发生,或者,由于您创建了字段 public
,当字段是第一次访问,以先到者为准。 但不是更早。换句话说,这个初始化已经和所有其他尝试执行延迟初始化的尝试一样懒惰。
此初始化的安全性由 JLS §12.4.2, Detailed Initialization Procedure
给出 <块引用>对于每个类或接口C
,都有一个唯一的初始化锁
LC
。从 C
到 LC
的映射由
Java 虚拟机实现。初始化 C
的过程如下
如下:
LC
同步初始化锁 C
。这涉及等到当前线程可以获取 LC
。Class
的 C
对象指示其他线程正在进行 C
的初始化,则释放 LC
并阻塞当前线程,直到通知正在进行的初始化已完成,此时重复此步骤。Class
的 C
对象指示当前线程正在对 C
进行初始化,那么这必须是一个递归的初始化请求。释放 LC
并正常完成。Class
的 C
对象指示 C
已经初始化,则不需要进一步的操作。释放 LC
并正常完成。…
这是一个过程,对类引用的每次解析都遵循,获取类的唯一初始化锁,并在类已经初始化或当前线程是执行初始化的线程时释放它。这个锁保证了线程安全,不管字段是否声明了final
,只要只在类初始化时写入即可。
为什么它仍然是实现单例的最有效方式的原因在同一章中给出:
<块引用>当可以确定类的初始化已经完成时,实现可以通过省略步骤 1 中的锁获取(并在步骤 4/5 中释放)来优化此过程,前提是,就内存模型,所有在获取锁时存在的先发生后排序,在执行优化时仍然存在。
由于每个类只初始化一次,然后与初始化时间相比,在这个初始化状态下使用了很长时间,因此这种优化具有非常大的影响,因此,自第一个 Java 版本以来,这是最先进的。但是,正如这篇笔记所说,优化不能破坏线程安全
答案 2 :(得分:0)
即使我们从final
中删除public static final Singleton instance = new Singleton ();
,也是线程安全的。
这是因为JVM保证将在任何线程访问静态instance
变量之前创建实例。