⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 小哈学Java 「犬小哈」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

先说结论

单例模式 5 种主流写法:饿汉式、懒汉式、双重检查锁(DCL)、静态内部类、枚举。

最佳实践:用枚举。 JVM 原生保证线程安全、反射安全、序列化安全,代码还最短。不能用枚举的场景(比如需要继承),退而求其次选静态内部类。

其余三种要么有缺陷、要么性能差、要么写起来啰嗦——了解原理即可,别在新代码里用。

5 种写法逐个过

饿汉式:简单但浪费

public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}

类加载时就创建实例,利用 JVM 类加载的线程安全性。

优点:实现简单,天然线程安全。缺点:不管用不用都会创建,如果初始化耗资源就浪费了。适合实例轻量且启动后一定会用的场景。

懒汉式:经典反面教材

// ❌ 线程不安全——多线程下会创建多个实例
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}

给方法加 synchronized 能解决线程安全,但每次调用都加锁,性能直接拉垮。面试知道就行,别真用。

双重检查锁:能用但啰嗦

public class DCLSingleton {
private static volatile DCLSingleton instance; // volatile 必须加
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) { // 第一次检查,避免不必要的同步
synchronized (DCLSingleton.class) {
if (instance == null) { // 第二次检查,确保唯一
instance = new DCLSingleton();
}
}
}
return instance;
}
}

volatile 不能省。没有它,线程可能拿到一个半初始化的对象——指令重排导致引用先赋值、构造函数后执行。

延迟加载 + 性能 OK,但代码复杂度高。有更优雅的方案,没必要选它。

静态内部类:懒加载的优雅解

public class HolderSingleton {
private HolderSingleton() {}
private static class Holder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE;
}
}

原理:静态内部类只有在被主动引用时才会加载。调 getInstance() 触发 Holder 类加载,由 JVM 保证线程安全。

优点:延迟加载、无锁、线程安全、代码简洁。缺点:无法防御反射攻击(不过实际项目中几乎没人故意用反射破单例)。

非枚举方式的首选。

枚举:终极答案

public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Business logic here.");
}
}
// 使用:EnumSingleton.INSTANCE.doSomething();

三句话说完为什么它是最佳实践:

  1. 线程安全:JVM 保证枚举常量只创建一次
  2. 反射安全:JDK 禁止通过反射创建枚举实例,直接抛异常
  3. 序列化安全:枚举的序列化机制特殊,反序列化时通过 valueOf() 返回已有实例,不会产生新对象

Josh Bloch(《Effective Java》作者)的原话:单元素枚举是实现单例的最佳方式。

怎么选?

写法 线程安全 懒加载 防反射 防序列化破坏 推荐度
饿汉式 ✅ ❌ ❌ ❌ ⭐⭐
懒汉式 ❌/慢 ✅ ❌ ❌ ⭐
双重检查锁 ✅ ✅ ❌ ❌ ⭐⭐⭐
静态内部类 ✅ ✅ ❌ ❌ ⭐⭐⭐⭐
枚举 ✅ ✅ ✅ ✅ ⭐⭐⭐⭐⭐

无特殊需求 → 枚举。不能用枚举 → 静态内部类。其余三种 → 了解原理即可。

文章目录
  1. 1. 先说结论
  2. 2. 5 种写法逐个过
    1. 2.1. 饿汉式:简单但浪费
    2. 2.2. 懒汉式:经典反面教材
    3. 2.3. 双重检查锁:能用但啰嗦
    4. 2.4. 静态内部类:懒加载的优雅解
    5. 2.5. 枚举:终极答案
  3. 3. 怎么选?