引言:为什么需要单例模式
在软件开发中,某些对象只需要一个全局实例:
- 数据库连接池
- 配置管理器
- 日志记录器
- 线程池
- 缓存系统
使用new
关键字多次创建这些对象会导致:
单例模式正是为解决这类问题而生的设计模式。它确保一个类只有一个实例,并提供全局访问点。本文将深入剖析单例模式的原理、实现及高级应用场景。
一、模式定义与核心思想
1.1 官方定义
单例模式 (Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。
1.2 设计哲学
核心原则:
- 私有构造器:防止外部实例化
- 静态实例:全局唯一实例
- 全局访问点:提供获取实例的方法
二、单例模式实现方式大全
2.1 实现方案对比
实现方式 | 线程安全 | 延迟加载 | 序列化安全 | 反射安全 | 复杂度 |
---|---|---|---|---|---|
饿汉式 | 是 | 否 | 否 | 否 | ★☆☆ |
懒汉式(非线程安全) | 否 | 是 | 否 | 否 | ★☆☆ |
同步方法 | 是 | 是 | 否 | 否 | ★★☆ |
双重检查锁 | 是 | 是 | 否 | 否 | ★★★ |
静态内部类 | 是 | 是 | 否 | 否 | ★★☆ |
枚举 | 是 | 否 | 是 | 是 | ★☆☆ |
2.2 饿汉式(Eager Initialization)
public class EagerSingleton {
// 类加载时即初始化
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造器
private EagerSingleton() {}
// 全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
特点:
- 线程安全(JVM保证类加载的线程安全)
- 不支持延迟加载
- 简单直接
2.3 懒汉式(Lazy Initialization - 非线程安全)
public class UnsafeLazySingleton {
private static UnsafeLazySingleton instance;
private UnsafeLazySingleton() {}
public static UnsafeLazySingleton getInstance() {
if (instance == null) {
instance = new UnsafeLazySingleton();
}
return instance;
}
}
风险:多线程环境下可能创建多个实例
2.4 同步方法(Thread-Safe Lazy)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
// 同步方法保证线程安全
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
缺点:每次获取实例都需要同步,性能差
2.5 双重检查锁(Double-Checked Locking)
public class DCLSingleton {
// volatile保证可见性和有序性
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (DCLSingleton.class) {
if (instance == null) { // 第二次检查
instance = new DCLSingleton();
}
}
}
return instance;
}
}
关键点:
volatile
防止指令重排序- 两次判空减少同步开销
2.6 静态内部类(Initialization-on-demand Holder)
public class HolderSingleton {
private HolderSingleton() {}
// 静态内部类持有实例
private static class SingletonHolder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理:利用类加载机制保证线程安全,实现延迟加载
2.7 枚举(Enum Singleton - 最佳实践)
public enum EnumSingleton {
INSTANCE;
// 业务方法
public void businessMethod() {
System.out.println("Singleton business logic");
}
}
优势:
- 线程安全
- 序列化安全
- 反射安全
- 简洁明了
三、单例模式进阶挑战
3.1 序列化与反序列化安全
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static SerializableSingleton instance = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
return instance;
}
// 防止反序列化创建新实例
protected Object readResolve() {
return instance;
}
}
3.2 反射攻击防护
public class ReflectionProofSingleton {
private static ReflectionProofSingleton instance;
private ReflectionProofSingleton() {
// 防止反射创建实例
if (instance != null) {
throw new IllegalStateException("Singleton already initialized");
}
}
public static synchronized ReflectionProofSingleton getInstance() {
if (instance == null) {
instance = new ReflectionProofSingleton();
}
return instance;
}
}
3.3 克隆安全
public class CloneSafeSingleton implements Cloneable {
private static CloneSafeSingleton instance = new CloneSafeSingleton();
private CloneSafeSingleton() {}
public static CloneSafeSingleton getInstance() {
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton cannot be cloned");
}
}
四、多线程环境下的单例
4.1 性能对比测试
barChart
title 单例模式性能对比(1000万次获取)
x-axis 实现方式
y-axis 时间(ms)
series 耗时
EagerSingleton: 32
EnumSingleton: 35
HolderSingleton: 38
DCLSingleton: 45
SynchronizedSingleton: 1200
4.2 单例与线程池
public class ThreadPoolSingleton {
private static final int CORE_POOL_SIZE = 5;
private static volatile ExecutorService instance;
private ThreadPoolSingleton() {}
public static ExecutorService getInstance() {
if (instance == null) {
synchronized (ThreadPoolSingleton.class) {
if (instance == null) {
instance = new ThreadPoolExecutor(
CORE_POOL_SIZE,
CORE_POOL_SIZE,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()
);
}
}
}
return instance;
}
}
4.3 分布式环境挑战
解决方案:
- 使用分布式锁实现全局单例
- 依赖外部存储维护状态
五、单例模式应用场景
5.1 典型应用场景
场景 | 单例应用 | 优势 |
---|---|---|
配置管理 | 全局配置读取 | 统一配置源 |
日志系统 | 日志记录器 | 避免重复创建 |
数据库连接 | 连接池管理 | 资源复用 |
缓存系统 | 全局缓存 | 数据一致性 |
硬件接口 | 设备控制 | 避免冲突 |
5.2 使用时机判断
当满足以下条件时考虑单例模式:
- 类需要全局唯一实例
- 需要严格控制资源访问
- 需要共享状态或数据
- 需要频繁访问的对象
5.3 不适用场景
- 需要多实例的类
- 需要扩展的子类
- 测试驱动开发(难以模拟)
- 分布式系统(需特殊处理)
六、模式优劣辩证
6.1 优势 ✅
6.2 劣势 ❌
- 违反单一职责:兼具创建和管理功能
- 测试困难:难以模拟和替换
- 隐藏依赖:增加耦合度
- 并发挑战:需要额外处理线程安全
- 生命周期管理:何时销毁实例
七、单例模式与依赖注入
7.1 单例 vs 依赖注入容器
7.2 Spring中的单例
@Service // Spring默认单例作用域
public class OrderService {
// 业务逻辑
}
@RestController
public class OrderController {
private final OrderService orderService;
// 通过构造器注入单例
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
}
7.3 最佳整合实践
public class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() {}
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
// 注册到Spring容器
@Bean
public DatabaseConnection databaseConnection() {
return DatabaseConnection.getInstance();
}
}
八、在开源框架中的应用
8.1 Java Runtime类
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
8.2 Log4j2 LoggerContext
public class LoggerContext {
private static LoggerContext context;
public static LoggerContext getContext() {
if (context == null) {
synchronized (LoggerContext.class) {
if (context == null) {
context = new LoggerContext();
}
}
}
return context;
}
}
8.3 Spring ApplicationContext
public class AnnotationConfigApplicationContext {
// 虽然不是严格单例,但通常作为单例使用
}
// 典型使用方式
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
// 创建单例ApplicationContext
ApplicationContext context = SpringApplication.run(MyApp.class, args);
}
}
九、单例模式反模式与陷阱
9.1 常见错误实现
// 反例1:public字段暴露
public class PublicFieldSingleton {
public static final PublicFieldSingleton INSTANCE = new PublicFieldSingleton();
private PublicFieldSingleton() {}
}
// 反例2:静态块未处理异常
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception e) {
// 未处理异常
}
}
private StaticBlockSingleton() {}
public static StaticBlockSingleton getInstance() {
return instance;
}
}
9.2 单例滥用案例
// 反例:将业务服务设计为单例
public class UserService {
private static UserService instance;
private UserService() {}
// 单例访问点
public static synchronized UserService getInstance() {
if (instance == null) {
instance = new UserService();
}
return instance;
}
// 业务方法
public void registerUser(User user) {
// ...
}
}
问题:
- 难以扩展
- 无法模拟测试
- 状态污染风险
十、最佳实践指南
10.1 实现选择建议
10.2 线程安全实践
优先使用不可变状态
public class ImmutableSingleton { private final Map<String, String> config; private ImmutableSingleton() { // 初始化后不再修改 config = loadConfigFromFile(); } public String getConfig(String key) { return config.get(key); } }
使用ThreadLocal实现线程单例
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> instance = ThreadLocal.withInitial(ThreadLocalSingleton::new); private ThreadLocalSingleton() {} public static ThreadLocalSingleton getInstance() { return instance.get(); } }
10.3 测试策略
public class DatabaseConnection {
private static DatabaseConnection instance;
// 测试钩子
static void setTestInstance(DatabaseConnection testInstance) {
instance = testInstance;
}
// 重置为生产实例
static void reset() {
instance = null;
}
}
// 测试类
class DatabaseConnectionTest {
@AfterEach
void tearDown() {
DatabaseConnection.reset();
}
@Test
void testSingleton() {
DatabaseConnection.setTestInstance(mock(DatabaseConnection.class));
// 执行测试
}
}
十一、总结:单例模式的核心价值
单例模式通过全局唯一实例实现了:
设计启示:
单例不是银弹,而是特定场景下的精密工具 - 用对场景比实现更重要
正如《设计模式》作者GoF所强调:
“单例模式确保一个类仅有一个实例,并提供一个访问它的全局访问点。这个模式在需要控制资源或者协调操作时特别有用”
扩展思考:
- 如何在微服务架构中实现全局单例?
- 单例模式如何与反应式编程结合?
- 单例对象的内存泄漏如何预防?
附录:单例模式快速参考卡
场景 | 推荐实现 | 注意事项 |
---|---|---|
简单应用 | 枚举单例 | 最佳实践首选 |
延迟加载 | 静态内部类 | 简洁安全 |
高性能要求 | 双重检查锁 | 需加volatile |
线程隔离 | ThreadLocal | 非全局单例 |
Spring环境 | @Bean单例 | 利用容器管理 |
单例模式是构建高效、一致系统的关键工具,在资源管理、配置控制等场景中具有不可替代的价值。