---
title: Java 设计模式详解：单例模式 (Singleton Pattern)
published: 2025-12-16
description: 深入解析单例模式的定义、核心要素与应用场景，详细对比饿汉式、懒汉式、双重检查锁 (DCL)、静态内部类及枚举等五种实现方式的线程安全性与底层原理。
tags: ["Java", "Design Patterns", "Singleton", "Best Practices", "Multi-threading"]
category: "Software Architecture"
pinned: false
encrypted: false

---

# 单例模式 (Singleton Pattern)

## 模式定义

单例模式（Singleton Pattern）是一种创建型设计模式，其核心目的是确保一个类在整个系统中**只有一个实例**，并提供一个**全局访问点**。

### 核心三要素
1.  **唯一性**：确保一个类只有一个实例。
2.  **自控性**：类必须自行创建这个实例（Self-instantiation）。
3.  **全局性**：必须向整个系统提供这个实例。

## 实现原理与规范

### 如何确保唯一实例？

要实现单例，必须遵循以下代码规范：

1.  **私有构造方法**：使用 `private` 修饰构造函数，禁止外部通过 `new` 关键字实例化。
2.  **私有静态变量**：使用 `private static` 成员变量持有当前类的唯一实例。
3.  **公有静态方法**：提供一个 `public static` 方法（通常命名为 `getInstance()`），向外界返回该实例。

### 典型应用场景

单例模式通常用于管理**无状态**的工具类或**共享资源**，因为有状态的单例在多线程环境下容易产生数据竞争。

-   **序列号生成器**：保证ID全局唯一。
-   **Web 页面计数器**：确保计数准确，不因刷新而重置。
-   **资源管理器**：如 IO 读写、数据库连接池（Database Connection Pool）。
-   **日志应用**：Log4j 等日志框架，保证日志文件的独占读写。
-   **配置中心**：全局配置文件的读取与缓存。

## 单例模式的优缺点

### 优点
1.  **资源高效**：对于重量级资源（如数据库连接、线程池），避免了频繁创建和销毁的开销。
2.  **数据一致性**：全局唯一的实例可以避免多重状态导致的逻辑冲突。
3.  **避免竞争**：在多线程环境下，集中管理对共享资源的访问（如写文件）。
4.  **简化访问**：提供了全局访问点，降低了模块间的耦合。

### 潜在风险（多实例可能性）
虽然单例模式旨在保证唯一性，但在以下极端情况下可能失效：
-   **分布式环境**：每个 JVM 都有自己的单例实例。
-   **类加载器**：同一个 JVM 中，不同的 ClassLoader 加载同一个类，会产生不同的实例。
-   **序列化与反序列化**：反序列化默认会创建新对象（需重写 `readResolve` 方法）。

---

## 核心实现方式详解

### 1. 饿汉式 (Eager Initialization)

类加载时就完成实例化。

```java
class Singleton {
    // 类加载时立即初始化，线程安全
    private static Singleton singleton = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }
}
```
-   **优点**：实现简单，JVM 层面保证线程安全。
-   **缺点**：若该类从未被使用，会造成内存浪费。

### 2. 懒汉式 (Lazy Initialization)

第一次调用时才初始化。

```java
class Singleton2 {
    private static Singleton2 singleton2;
    private Singleton2() {}

    // 使用 synchronized 锁住整个方法，防止多线程同时进入导致创建多个实例
    public synchronized static Singleton2 getInstance() {
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}
```
-   **优点**：延迟加载，节约资源。
-   **缺点**：锁粒度太大，每次获取实例都要加锁，性能极差，不推荐在高并发场景使用。

### 3. 双重检查锁 (Double-Checked Locking, DCL)

懒汉式的性能优化版本。

```java
class Singleton3 {
    // 必须使用 volatile 关键字，防止指令重排
    private volatile static Singleton3 singleton3;
    
    private Singleton3() {}

    public static Singleton3 getInstance() {
        // 第一重检查：如果已经创建，直接返回，避免不必要的锁
        if (singleton3 == null) {
            synchronized(Singleton3.class) {
                // 第二重检查：防止A、B线程同时通过第一层检查，A释放锁后B重复创建
                if (singleton3 == null) {
                    singleton3 = new Singleton3();
                }
            }
        }
        return singleton3;
    }
}
```

#### 💡 深度解析：为什么需要 `volatile`？

在 `singleton3 = new Singleton3();` 这行代码执行时，JVM 实际上进行了三步操作：
1.  **分配内存** (Allocate memory)。
2.  **初始化对象** (Initialize object)。
3.  **指向地址** (Point pointer to memory)。

**问题**：如果没有 `volatile`，CPU 或编译器可能进行**指令重排序**，将顺序变为 `1 -> 3 -> 2`。
-   **场景**：线程 A 执行了 1 和 3（此时 `singleton3` 已经非 null，但未初始化），然后时间片结束。
-   **后果**：线程 B 进来判断 `singleton3 != null`，直接拿走了一个**未初始化**的对象去使用，导致空指针异常或其他错误。
-   **解决**：`volatile` 关键字通过内存屏障禁止指令重排序，保证初始化完成后才赋值。

### 4. 静态内部类 (Static Inner Class)

推荐的优雅实现方式。

```java
public class InnerClassSingleton {
    private InnerClassSingleton() {
        // 防止反射攻击（可选安全防御）
        if (SingletonHolder.INSTANCE != null) {
            throw new IllegalStateException("Singleton already initialized");
        }
    }
    
    // 静态内部类：只有在调用 getInstance 时才会被加载
    private static class SingletonHolder {
        // 由 JVM 类加载机制保证线程安全
        static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
```
-   **优点**：结合了饿汉式的线程安全（JVM 机制）和懒汉式的延迟加载优势。

### 5. 枚举 (Enum)

《Effective Java》作者推荐的最佳方式。

```java
public enum EnumSingleton {
    INSTANCE;
    
    public void doSomething() {
        System.out.println("Processing...");
    }
}
```
-   **优点**：
    -   **绝对防止多次实例化**：即使是反射也无法破坏枚举的单例性。
    -   **自动支持序列化**：无需担心反序列化生成新对象。

---

## 框架中的单例模式

在现代框架中，单例对象的管理通常交给容器（IoC Container）。

### Spring Framework

Spring 默认的 Bean 作用域（Scope）就是单例。

```java
@Configuration
public class AppConfig {
    
    @Bean // 默认 scope = singleton
    public DataSource dataSource() {
        // 由 Spring 容器管理，确保全局只有一个 HikariDataSource 实例
        return new HikariDataSource();
    }
}
```

## 总结

| 实现方式          | 线程安全 | 延迟加载 | 性能 | 推荐指数 |
| :---------------- | :------- | :------- | :--- | :------- |
| 饿汉式            | 是       | 否       | 高   | ⭐⭐⭐      |
| 懒汉式 (Sync方法) | 是       | 是       | 低   | ⭐        |
| 双重检查锁 (DCL)  | 是       | 是       | 高   | ⭐⭐⭐⭐     |
| 静态内部类        | 是       | 是       | 高   | ⭐⭐⭐⭐⭐    |
| 枚举              | 是       | 否       | 高   | ⭐⭐⭐⭐⭐    |

**最佳实践建议**：
-   如果不需要延迟加载，且为了防止反射/序列化破坏，首选 **枚举**。
-   如果需要延迟加载，首选 **静态内部类**。
-   如果在维护旧代码，可能会遇到 **DCL**，务必检查是否加了 `volatile`。