具有内部类的Java Singleton - 什么保证了线程安全?

2022-09-04 21:05:21

实现单例的一种常见 (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 没有提到静态字段具有任何发生在之前的关系。我如何确定初始化只会发生一次,并且所有线程都看到相同的实例?


答案 1

它在Java并发实践中有很好的描述:

惰性初始化持有者类 idiom 使用其唯一目的是初始化 Resource 的类。JVM 会推迟初始化 ResourceHolder 类,直到它实际使用 [JLS 12.4.1],并且由于 Resource 是使用静态初始值设定项初始化的,因此不需要额外的同步。任何线程对 getresource 的第一次调用都会导致 ResourceHolder 被加载和初始化,此时 Resource 的初始化通过静态初始值设定项进行。

静态初始化

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


答案 2

我们首先需要了解两点:

加载类时,静态初始化仅发生一次

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

....

类的初始化包括执行其静态初始值设定项和类中声明的静态字段(类变量)的初始值设定项

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

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

对于每个类或接口 C,都有一个唯一的初始化锁 LC。从 CLC 的映射由 Java 虚拟机实现自行决定。

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

有关初始化锁的更多信息,请阅读 JSL 17