类加载顺序与实例化过程:
类加载过程: 在 Java 中,类加载过程涉及以下几个步骤:
加载:JVM 加载类的字节码。链接:包括验证、准备和解析。初始化:初始化类的静态成员变量和执行静态代码块。静态内部类方式利用了类的 初始化 过程来确保实例的唯一性和线程安全。具体来说,内部类是延迟加载的,也就是说,只有当 getInstance() 方法第一次被调用时,SingletonHolder 类才会被加载,进而实例化 INSTANCE。
具体的执行逻辑:
public class Singleton {
// 静态内部类,只有在调用 getInstance 时才会加载
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
// 私有构造函数,防止外部实例化
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
类的加载与实例化:
当 JVM 加载 Singleton 类时,SingletonHolder 类并不会立刻加载。只有当第一次调用 getInstance() 方法时,SingletonHolder 类才会被加载,JVM 会初始化它,从而初始化 INSTANCE。INSTANCE 是 SingletonHolder 类的一个静态成员,在类加载过程中会被初始化成一个唯一的 Singleton 实例。
初始化 INSTANCE 时的线程安全:
由于 **SingletonHolder`` 是静态内部类**,并且它的实例是在类加载时被初始化的,所以 INSTANCE` 的初始化是 线程安全的。JVM 在加载静态内部类时,会自动处理线程安全问题,这意味着只会有一个线程能成功地初始化 INSTANCE,其他线程在加载 SingletonHolder 类时会发现 INSTANCE 已经被创建,不会再重新创建它。为什么不需要显式加锁?
类加载的线程安全性:
在 Java 中,类的加载过程本身是线程安全的。JVM 保证在同一时间,只有一个线程会执行类的初始化过程。这意味着,SingletonHolder 的初始化会在多线程环境下自动处理线程安全的问题。
SingletonHolder.INSTANCE 的初始化:
当 getInstance() 被第一次调用时,SingletonHolder 类的加载过程才会开始。在这个过程中,INSTANCE 会被创建并赋值。这是由 JVM 保证的 单例初始化,只会发生一次。如果多个线程同时调用 getInstance() 方法,JVM 会保证只有一个线程能够初始化 INSTANCE,其他线程会获取已经初始化的实例。因此,不需要显式加锁来防止多线程同时创建实例。
JVM 的类初始化机制:
类加载是 延迟初始化 的。即 SingletonHolder 在 getInstance() 被调用时才会加载,因此实例的创建是 延迟的,并且在此期间不会有竞争条件,因为类加载本身是同步的。一旦 INSTANCE 被初始化,JVM 会确保后续的访问都能返回相同的 Singleton 实例。总结:
类加载顺序:
当 getInstance() 方法第一次被调用时,SingletonHolder 类才会被加载,进而初始化 INSTANCE。JVM 保证 SingletonHolder 的初始化是线程安全的,且只会初始化一次。
为什么不需要显式加锁:
Java 的类加载机制本身是线程安全的,类的初始化过程会确保只有一个线程会初始化 SingletonHolder.INSTANCE,其他线程会看到已经初始化的实例。因此,不需要在 getInstance() 中加锁,因为 JVM 会自动处理初始化的线程安全问题。通过这种方式,我们既能实现 懒加载,即仅在第一次调用时才创建实例,又能避免显式的同步锁,从而提高性能。