单例不只是把构造方法设为 private。还要回答:对象何时创建,并发第一次访问时会不会重复创建,以及其他线程能否安全看到初始化结果。
提前创建与延迟创建
静态字段直接初始化,是常见的饿汉式写法:
public final class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
它在类初始化时创建,而不是简单等同于“JVM 一启动就创建”。懒汉式延迟到首次访问,但普通的 if (instance == null) 不加同步,会让多个线程同时通过检查。
同步方法最容易解释
public final class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
返回应在 if 之后,确保已经初始化时仍返回实例。同步覆盖检查和创建,也建立可见性关系。是否值得优化访问开销,应按实际场景度量,不能凭模式名称断言它“性能一定很差”。
双重检查为什么需要 volatile
public final class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}
外层检查减少初始化后的加锁;内层检查防止等待锁的线程再次创建。volatile 参与安全发布,不能因为创建发生在 synchronized 内,就省掉外部读取所需的可见性保证。这里按 Java 5 及之后的内存模型理解。JLS 17.4.5
静态 Holder 利用类初始化机制
public final class HolderSingleton {
private HolderSingleton() {}
private static class Holder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE;
}
}
访问 Holder.INSTANCE 时触发持有类的初始化,由类初始化机制协调并发。它把延迟创建与安全初始化交给 JVM,而不是手写双重检查。JLS 类初始化过程
唯一实例不等于所有操作都线程安全
这些常规写法的范围与类加载器有关,不是整个进程不可能出现第二个同类实例的安全边界。反射、序列化及框架作用域还有各自约束。
更重要的是:实例只创建一次,不代表里面的可变集合、计数器或复合业务操作自动安全。初始化、发布与后续状态并发必须分别设计。