饿汉式类加载即初始化、线程安全但可能浪费资源;懒汉式首次调用才创建、需volatile+双重检查锁防重排序;静态内部类实现天然懒加载与线程安全;枚举单例最安全简洁但无法传参或继承。

饿汉式单例:类加载时就初始化,线程安全但可能浪费资源
饿汉式在 static 字段声明时直接 new 实例,JVM 类加载阶段就完成初始化。它天然线程安全,不依赖同步机制,但缺点也很实在:哪怕从没调用过 getInstance(),实例也已创建并常驻内存。
- 适用于实例创建成本低、且几乎肯定会用到的场景(比如配置管理器、日志工厂)
-
private static final Singleton instance = new Singleton();这行必须是static final,否则无法保证唯一性和不可变性 - 如果构造函数里有耗时操作(如读文件、连数据库),首次类加载会明显卡顿,且无法按需延迟——这点常被忽略
懒汉式单例:第一次调用才创建,但默认线程不安全
懒汉式把实例创建推迟到 getInstance() 第一次被调用时,节省资源,但原始写法(只加 if 判断)在多线程下会生成多个实例——这是最典型的坑。
- 错误写法:
if (instance == null) instance = new Singleton();—— 多个线程同时通过 if 判断后,都会执行 new - 基础修复:给整个方法加
synchronized,但会严重拖慢性能(每次调用都锁) - 推荐方案:双重检查锁定(DCL),但必须给
instance加volatile修饰符,否则因指令重排序仍可能出错 - 示例关键行:
private static volatile Singleton instance;和if (instance == null) { synchronized (Singleton.class) { if (instance == null) instance = new Singleton(); } }
为什么不用静态内部类?它其实是更优的懒汉替代方案
静态内部类利用 JVM 类加载机制实现“天然懒加载 + 天然线程安全”,比 DCL 更简洁、无同步开销、也不用记 volatile。
- 核心逻辑:
private static class Holder { static final Singleton INSTANCE = new Singleton(); },然后getInstance()返回Holder.INSTANCE - JVM 保证
Holder类只在首次主动使用(即调用getInstance())时才加载和初始化,且类加载过程本身是线程安全的 - 没有同步块,没有 volatile,没有竞态风险,代码干净,性能接近饿汉式
- 注意:不能在
Holder中引用外部类的非 static 成员,否则会破坏懒加载语义
枚举单例:最简最安全,但无法继承、无法延迟初始化参数
用 enum 实现单例是 Effective Java 推荐的方式,JVM 层面保障单例性,还能天然防反射和反序列化攻击。
立即学习“Java免费学习笔记(深入)”;
- 写法极简:
public enum Singleton { INSTANCE; },调用直接Singleton.INSTANCE - 反射无法绕过枚举构造限制,
Enum.valueOf()也受控,反序列化时 JVM 自动返回已有实例 - 但枚举类不能继承其他类(Java 不支持多重继承),也无法在构造时传参(如读取配置文件路径)
- 如果单例需要初始化参数或依赖注入,枚举就不适用,得退回静态内部类或带参数的懒汉式
真正难的不是写出两种写法,而是判断该用哪一种:饿汉式适合轻量且必用;懒汉式 DCL 容易漏掉 volatile;静态内部类最平衡;枚举最安全但灵活性最低。选错不是语法问题,而是对初始化时机、线程模型和扩展需求的理解偏差。










