饿汉式单例天生线程安全,因类加载时即初始化实例,static字段仅赋值一次,jvm保证原子性与不可重排序;枚举单例则天然防反射、防序列化、线程安全;静态内部类实现延迟加载最简洁可靠。

饿汉式单例为什么天生线程安全
因为类加载时就初始化实例,static字段只被赋值一次,JVM 保证这个过程原子且不可重排序。只要不反射破坏、不序列化绕过,它就是最省心的单例。
常见错误现象:有人给饿汉式加 synchronized 方法或双重检查锁,纯属画蛇添足——不仅没增益,反而干扰 JIT 优化,还可能掩盖对类加载机制的理解偏差。
- 适用场景:实例创建开销小、依赖资源少(比如工具类、配置读取器)
- 注意
static块中不能抛受检异常,否则需包装成RuntimeException - 如果构造函数里调用外部服务(如数据库连接),会导致类加载阻塞,影响启动速度
懒汉式不加同步一定会出问题
多个线程同时走到 if (instance == null) 判断,都通过后各自执行 new Singleton(),最终生成多个实例。这不是“概率低”,而是确定性 bug。
典型错误写法:getInstance() 方法体里只做判空 + new,没加任何同步控制。
- 最简修复是把方法声明为
synchronized,但会锁整个方法,性能差 - 想兼顾性能就得用双重检查锁(DCL),但必须给
instance加volatile——否则可能因指令重排,让其他线程看到未初始化完成的对象 - JDK 1.5+ 才真正支持
volatile的禁止重排语义,老版本 DCL 是无效的
枚举单例怎么绕过所有坑
Java 枚举天生防反射、防序列化、线程安全,且写法极简。它不是“语法糖”,而是 JVM 层面保障的单例实现方式。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
错误认知:认为枚举只是“看起来像单例”,其实 Singleton.INSTANCE 在类加载阶段就被 JVM 初始化,且仅此一次。
- 不能被反射调用私有构造器(
Enum类构造器被 JVM 特殊保护) - 反序列化时 JVM 直接返回已有枚举常量,不会调用
readObject - 唯一缺点:无法继承(因为枚举隐式继承
java.lang.Enum),也不能实现延迟加载
为什么推荐用静态内部类实现延迟加载
它利用了 JVM 类加载的懒加载特性:外部类加载时,静态内部类不加载;只有首次调用 Holder.INSTANCE 时,Holder 类才被加载并初始化 static 实例。
这比 DCL 更干净——不用 volatile、不写同步块、无重排风险,还能做到真正的按需初始化。
- 关键点:内部类必须是
static,否则会持有外部类引用,导致无法卸载 - 不能在内部类里访问外部类的非
static成员,否则编译失败 - 如果单例需要传参初始化(比如从配置读端口),静态内部类方案就不再适用,得退回到带参数的 DCL 或工厂模式
真正容易被忽略的是:单例是否真的需要“全局唯一”。很多场景下所谓“单例”其实是误用——比如 DAO 层对象,本该由 Spring 管理生命周期,硬写成手动单例反而导致事务、AOP 失效。先确认需求是不是真要跨 ClassLoader 全局唯一,再选实现方式。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










