饿汉式天生线程安全,因static字段在类加载时由JVM串行初始化;DCL必须用volatile禁止重排序;枚举单例由JVM保障原子性、防反射和反序列化;静态内部类实现懒加载但可能被间接引用提前触发。

饿汉式为什么天生线程安全
因为 static 字段在类加载阶段就完成初始化,而类加载由 JVM 保证串行执行,不存在竞态条件。只要不手动调用 Class.forName(...) 触发多次加载(正常情况不会),实例就唯一且立即可用。
但要注意:即使没用到该单例,类一加载就会创建实例,可能浪费资源;也无法传递构造参数(除非改用静态代码块 + 私有构造器传参)。
public class SingletonEager {
private static final SingletonEager instance = new SingletonEager();
private SingletonEager() {}
public static SingletonEager getInstance() {
return instance;
}
}
双重检查锁(DCL)必须加 volatile
不加 volatile 会导致指令重排序:JVM 可能将 new SingletonLazy() 拆成「分配内存→设置引用→调用构造器」三步,而「设置引用」可能早于「构造器执行完毕」。其他线程看到非 null 的 instance,却访问到未初始化完成的对象,引发 NullPointerException 或状态不一致。
-
volatile禁止重排序,并保证可见性 - 第一次判空避免每次同步开销;第二次判空防止多线程重复初始化
- 同步块必须锁定
SingletonLazy.class或当前类的 Class 对象,不能锁this(此时对象还没造出来)
public class SingletonLazy {
private static volatile SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
枚举单例为何最简且防反射/反序列化攻击
JVM 保证枚举类型的实例创建是原子的、线程安全的,且天然防止反射调用私有构造器(Enum 的构造器被 JVM 特殊保护)、也自动阻止反序列化生成新实例(readObject 被禁用)。
立即学习“Java免费学习笔记(深入)”;
BJXShop网上购物系统是一个高效、稳定、安全的电子商店销售平台,经过近三年市场的考验,在中国网购系统中属领先水平;完善的订单管理、销售统计系统;网站模版可DIY、亦可导入导出;会员、商品种类和价格均实现无限等级;管理员权限可细分;整合了多种在线支付接口;强有力搜索引擎支持... 程序更新:此版本是伴江行官方商业版程序,已经终止销售,现于免费给大家使用。比其以前的免费版功能增加了:1,整合了论坛
它不是“语法糖”,而是 JVM 层级的保障。如果面试官问“还能怎么破坏枚举单例”,答案基本只有:修改字节码或用 Unsafe 绕过(超出常规 Java 范畴)。
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
// ...
}
}
静态内部类方式的隐含限制
利用类加载机制延迟初始化:外部类加载时,静态内部类不加载;只有首次调用 getInstance() 时,JVM 才加载并初始化 Holder 类,从而创建实例。线程安全、懒加载、无同步开销。
但要注意:如果外部类中其他静态字段或静态代码块触发了 Holder 类的间接引用(比如通过反射访问其 class 对象),可能导致提前初始化——这种情况极少见,但一旦发生,就破坏了“懒”的语义。
public class SingletonStaticInner {
private SingletonStaticInner() {}
private static class Holder {
private static final SingletonStaticInner INSTANCE = new SingletonStaticInner();
}
public static SingletonStaticInner getInstance() {
return Holder.INSTANCE;
}
}
真正容易被忽略的,是 DCL 中 volatile 的必要性,以及枚举单例在反序列化场景下无需额外实现 readResolve —— 这两点在高并发或分布式序列化场景里,往往决定单例是否真的“唯一”。









