问题 带有内部类的Java Singleton - 什么能保证线程安全?


一个常见的(12)实现单例的方法使用带有静态成员的内部类:

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

    public static Singleton getInstance() {    
        return SingletonHolder.instance;    
    }

    private Singleton() {
        //...
    }
}

据说这个实现是懒惰的初始化和线程安全的。但究竟是什么保证了它的线程安全? JLS 17 处理 线程和锁 没有提到静态字段有任何种类 之前发生 关系。我怎样才能确定初始化只发生一次并且所有线程都看到同一个实例?


3021
2017-10-03 10:03


起源

你可能想了解一下 需求持有人初始化。 - RealSkeptic
@nafas,不,实际上,这里的关键是只在需要时加载一个类。 - RealSkeptic
什么使它成为单身人士?是什么阻止了调用 new Singleton();? - c0der
明显。我认为它应该添加到代码中。 - c0der
你可以 也 想读 Singleton被认为是愚蠢的 - T.E.D.


答案:


我们首先需要了解两点:

发生静态初始化 只有一次 加载课程时

调用在其声明中具有static修饰符的字段 静态字段 或类变量。它们与类相关联,而不是与任何对象相关联。该类的每个实例共享一个类变量,该变量位于内存中的一个固定位置

....

类的初始化包括执行其静态初始化程序和类中声明的静态字段(类变量)的初始化程序

这意味着静态初始化程序在初始化对象类时只执行一次(实际的  对象,而不是类的实例)。

由于Java编程语言是多线程的,因此初始化类或接口需要仔细同步,因为其他一些线程可能正在尝试同时初始化同一个类或接口。

对于每个类或接口 C,有一个独特的初始化锁 LC。映射来自 C 至 LC 由Java虚拟机实现决定。

现在,简单来说,当两个线程尝试初始化时 instance 获得的第一个线程 LC 是实际初始化的那个 instnace,并且因为它静态地执行,java提供了它只发生一次的承诺。

有关初始化锁定读取的更多信息 JSL 17


4
2017-10-03 12:07





它的描述很好 Java并发实践

延迟初始化持有者类idiom使用一个类,其唯一目的是初始化Resource。 JVM推迟初始化ResourceHolder类,直到它为止   实际使用[JLS 12.4.1],因为资源已初始化   使用静态初始化程序,不需要额外的同步。   任何线程对getresource的第一次调用都会导致ResourceHolder   被加载和初始化,此时初始化   资源通过静态初始化程序发生。

静态初始化

静态初始化程序由JVM在类初始化时运行,   在类加载之后,但在任何线程使用该类之前。   因为JVM在初始化期间获取锁[JLS 12.4.2]和   这个锁是每个线程至少获取一次以确保   class已加载,静态初始化期间进行内存写入   对所有线程自动可见。因此静态初始化   对象在构造期间不需要显式同步   或被引用时。


7
2017-10-03 10:34