单例模式
单例模式用于保证一个类在整个 JVM 进程中只有一个实例,并提供一个全局访问点。
它适合管理全局唯一、创建成本高、需要共享状态或共享资源的对象,例如配置中心、线程池、连接池、日志组件等。
解决什么问题
如果一个对象被随意创建,可能出现这些问题:
- 多个实例持有不同状态,导致数据不一致。
- 对象创建成本高,频繁创建浪费资源。
- 某些资源本来就应该唯一,例如全局配置、线程池。
单例模式把“实例创建权”收回到类内部,让外部只能通过固定入口拿对象。
基本结构
text
Singleton
- private static instance
- private constructor
+ public static getInstance()关键点:
- 构造方法私有化,禁止外部
new。 - 实例由类自己保存。
- 提供静态方法获取实例。
- 多线程环境下必须保证线程安全。
推荐实现:静态内部类
静态内部类是 Java 中比较推荐的写法,兼顾懒加载和线程安全。
java
public final class AppConfig {
private AppConfig() {
}
private static class Holder {
private static final AppConfig INSTANCE = new AppConfig();
}
public static AppConfig getInstance() {
return Holder.INSTANCE;
}
}为什么线程安全:
- 外部类加载时不会立即加载
Holder。 - 第一次调用
getInstance()时才加载Holder。 - 类加载过程由 JVM 保证线程安全。
枚举单例
如果需要最强的防御能力,枚举单例也很常用。
java
public enum IdGenerator {
INSTANCE;
public long nextId() {
return System.currentTimeMillis();
}
}调用:
java
long id = IdGenerator.INSTANCE.nextId();枚举单例天然防止反射和反序列化破坏单例,缺点是写法不一定符合所有团队习惯。
不推荐的懒汉式
下面这种写法在多线程下可能创建多个实例:
java
public class UnsafeSingleton {
private static UnsafeSingleton instance;
private UnsafeSingleton() {
}
public static UnsafeSingleton getInstance() {
if (instance == null) {
instance = new UnsafeSingleton();
}
return instance;
}
}如果两个线程同时进入 if (instance == null),就可能各自创建一个对象。
双重检查锁
双重检查锁可以用,但必须加 volatile。
java
public class DatabaseClient {
private static volatile DatabaseClient instance;
private DatabaseClient() {
}
public static DatabaseClient getInstance() {
if (instance == null) {
synchronized (DatabaseClient.class) {
if (instance == null) {
instance = new DatabaseClient();
}
}
}
return instance;
}
}volatile 的作用是避免指令重排,防止其他线程拿到一个还没初始化完成的对象。
使用场景
- 全局配置读取器。
- 应用级缓存管理器。
- 线程池、连接池等共享资源。
- ID 生成器。
- Spring 容器中的默认单例 Bean。
优点
- 节省资源,避免重复创建。
- 提供统一访问入口。
- 可以控制实例生命周期。
缺点
- 容易变成全局变量,增加隐藏依赖。
- 单例内部如果有可变状态,多线程下仍然要考虑并发安全。
- 单元测试时不如普通对象容易替换。
- 过度使用会让代码耦合变重。
实战注意
在 Spring 项目里,大多数对象不需要自己手写单例。Spring Bean 默认就是单例作用域,通常让容器管理即可。
真正需要手写单例的场景,多出现在框架工具类、SDK、非 Spring 管理对象中。