设计模式入门:Java 单例模式(Singleton)详解,从入门到实战

一、什么是单例模式?

单例模式是 Java 中最基础、最常用的创建型设计模式 ,它的核心定义非常简单:
保证一个类在整个应用程序中,有且仅有一个实例对象,并且提供一个全局访问该实例的方法。

简单理解:就像一个程序里的「唯一工具」,比如项目中的配置管理器、数据库连接池、日志工具类,全程只需要一个对象统一管理,不能随意创建多个,避免资源浪费和逻辑混乱。

单例模式的核心特性

  1. 唯一实例:类只能被实例化一次,内存中永远只有一个对象;
  1. 全局访问:提供一个公共的静态方法,让外部可以直接获取这个唯一实例;
  1. 私有构造 :构造方法私有化,禁止外部通过 new 关键字创建对象。

二、为什么要用单例模式?(核心优势)

在实际开发中,单例模式能解决这些关键问题:

  1. 节约系统资源:避免频繁创建 / 销毁对象(如数据库连接、线程池),减少内存开销;
  1. 保证数据统一:全局只有一个实例,避免多个实例导致的数据不一致、状态冲突;
  1. 统一管理逻辑:适合全局配置、全局缓存、全局日志等需要统一控制的场景。

适用场景

  • 数据库连接池、Redis 连接池
  • 项目全局配置类
  • 日志工具类
  • 线程池、任务管理器
  • 计数器(统计在线人数、接口访问量)

三、Java 单例模式的 5 种实现方式

单例模式的实现有多种写法,我们从最简单到最安全、最高效,逐一讲解。

前置规则(所有写法都必须遵守)

  1. 构造方法私有化(private 修饰),杜绝外部 new;
  1. 内部创建唯一的私有实例;
  1. 提供公共静态方法(getInstance())供外部获取实例。

1. 饿汉式(静态常量)- 最简单、线程安全

核心思想 :类加载时就直接创建实例,天生线程安全,没有并发问题。

因为 "急切创建",所以叫饿汉式

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { // 1. 私有静态常量,类加载时就初始化 private static final Singleton INSTANCE = new Singleton(); // 2. 私有构造方法,禁止外部实例化 private Singleton() {} // 3. 公共静态方法,返回唯一实例 public static Singleton getInstance() { return INSTANCE; } } |

优点

  • 写法最简单
  • 类加载时初始化,线程安全
  • 没有锁,执行效率高

缺点

  • 类加载就创建实例,可能造成内存浪费(如果这个类一直不用,实例也会占内存)

使用建议:适用于实例占用内存小、一定会被使用的场景。

2. 懒汉式(线程不安全)- 按需创建,多线程禁用

核心思想 :用到实例时才创建(懒加载),但多线程环境下会产生多个实例,线程不安全

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { private static Singleton instance; private Singleton() {} // 外部调用时才创建实例 public static Singleton getInstance() { // 线程A和线程B同时判断到这里,会创建两个实例 if (instance == null) { instance = new Singleton(); } return instance; } } |

优点 :实现懒加载,节约内存
缺点多线程环境下完全不可用,会破坏单例特性

结论:工作中绝对不要用!

3. 懒汉式(同步方法,线程安全)- 效率低

核心思想 :给 getInstance() 方法加 synchronized 锁,保证线程安全,但效率极低

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { private static Singleton instance; private Singleton() {} // 方法加锁,同一时间只能有一个线程进入 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } |

优点 :线程安全,支持懒加载
缺点 :加锁粒度太大,每次获取实例都要锁,高并发下性能差

使用建议:不推荐用于高并发项目。

4. 双重检查锁(DCL)- 推荐!兼顾线程安全 + 效率 + 懒加载

全称 :Double Check Lock,简称 DCL
核心思想 :两次判空 + 一次锁 + volatile 关键字,既保证线程安全,又保证高性能,是企业开发最常用写法

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { // volatile 禁止指令重排,保证多线程下的可见性 private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { // 第一次检查:不加锁,快速判断,提高效率 if (instance == null) { // 加锁:保证同一时间只有一个线程创建实例 synchronized (Singleton.class) { // 第二次检查:防止多线程同时进入第一次检查后重复创建 if (instance == null) { instance = new Singleton(); } } } return instance; } } |

为什么要用 volatile?
instance = new Singleton() 不是原子操作,JVM 可能会指令重排,导致线程获取到未初始化完成的实例。volatile 可以禁止指令重排,保证线程安全。

优点

  • 线程安全
  • 懒加载(用到才创建)
  • 加锁粒度小,性能极高
  • 兼顾内存和效率

结论这是绝大多数场景的最优解,推荐优先使用!

5. 静态内部类 - 极简优雅,线程安全

核心思想:利用 JVM 类加载机制保证线程安全,写法极简,也是推荐方案。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { private Singleton() {} // 静态内部类,只有被调用时才会加载 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } |

原理

  • 外部类加载时,静态内部类不会立即加载;
  • 调用 getInstance() 时,内部类才会加载,JVM 保证类加载过程线程安全;
  • 天然实现懒加载 + 线程安全

优点

  • 代码极简
  • 线程安全
  • 懒加载
  • 无锁,性能极高

缺点:无法传递参数(不适合需要初始化参数的单例)

使用建议:不需要传参的场景,首选这种写法!

6. 枚举单例 - 最安全、防反射破坏

核心思想 :Java 枚举天然单例,绝对防止反射、序列化破坏单例,是《Effective Java》推荐的最佳写法。

|-----------------------------------------------------------------------------------------------------------------------------|
| java public enum Singleton { // 唯一实例 INSTANCE; // 可以添加自己的方法 public void doSomething() { System.out.println("枚举单例执行方法"); } } |

使用方式

|-----------------------------------------------------------------------|
| java Singleton instance = Singleton.INSTANCE; instance.doSomething(); |

优点

  • 最简单
  • 绝对线程安全
  • 防止反射攻击、序列化破坏
  • 无需考虑并发问题

缺点:不支持懒加载(枚举类加载时就创建实例)

使用建议:追求极致安全、不需要懒加载时,这是完美方案!

四、单例模式的安全问题与解决

1. 反射破坏单例

通过反射可以强制调用私有构造方法,创建新实例,破坏 DCL、静态内部类、饿汉式单例。

解决方案

  • 首选枚举单例(天然防反射)
  • 构造方法中加判断,重复创建抛出异常:

|----------------------------------------------------------------------------------------------------|
| java private Singleton() { if (instance != null) { throw new RuntimeException("禁止通过反射创建单例实例"); } } |

2. 序列化破坏单例

对象序列化后再反序列化,会创建新对象。

解决方案

  • 枚举单例天然防序列化
  • 普通单例类添加 readResolve() 方法:

|--------------------------------------------------------|
| java private Object readResolve() { return instance; } |

五、五种单例写法对比总结

|------------|------|-----|----|-------|
| 写法 | 线程安全 | 懒加载 | 性能 | 推荐指数 |
| 饿汉式 | ✅ | ❌ | 高 | ⭐⭐⭐ |
| 懒汉式(不安全) | ❌ | ✅ | 高 | ❌ |
| 懒汉式(同步方法) | ✅ | ✅ | 低 | ⭐ |
| 双重检查锁(DCL) | ✅ | ✅ | 极高 | ⭐⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | 极高 | ⭐⭐⭐⭐⭐ |
| 枚举单例 | ✅ | ❌ | 高 | ⭐⭐⭐⭐⭐ |

六、总结

  1. 单例核心:一个类只有一个实例,全局访问,私有构造;
  1. 最优通用方案:双重检查锁(DCL)、静态内部类;
  1. 最安全方案:枚举单例;
  1. 使用场景:全局配置、连接池、线程池、工具类;
  1. 避坑指南:避免使用线程不安全的懒汉式,高并发优先用 DCL。

单例模式是设计模式的入门基石,理解它的实现原理和优缺点,能帮你写出更健壮、更高效的 Java 代码。

总结

  1. 单例模式保证全局唯一实例 ,核心是私有构造 + 静态实例 + 全局获取方法
  1. 日常开发优先用 双重检查锁(DCL)静态内部类
  1. 追求极致安全选 枚举单例 ,简单场景选 饿汉式
  1. 警惕反射、序列化破坏单例,做好防御处理。
相关推荐
codingPower1 小时前
ApplicationListener 和 SpringApplicationRunListener 深度解析对比
java·开发语言·spring boot
ch.ju1 小时前
Java Programming Chapter 2-Recursion of function
java·开发语言
铁皮哥1 小时前
【后端开发】RabbitMQ、RocketMQ、Kafka 怎么选?我从业务场景重新梳理了一遍
java·linux·数据库·分布式·kafka·rabbitmq·rocketmq
suixinm1 小时前
Agent 设计模式:从 ReAct、CodeAct 到 Agentic Rag 与多智能体
设计模式·ai·react·rag·ai agent·agent智能体·multi-agent
AC赳赳老秦1 小时前
数据库操作自动化:用 OpenClaw 对接 Navicat/DBeaver,实现数据备份、脱敏、日常操作自动化
java·运维·数据库·python·oracle·自动化·openclaw
程序员小白条1 小时前
AI 编程辅助,从入门到真香
java·开发语言·数据库·人工智能·面试·职场和发展
曹牧1 小时前
Java:“Syntax error on token “do“, Identifier expected”
java·开发语言
geovindu1 小时前
go: Registry Pattern
开发语言·后端·设计模式·golang·注册模式
05候补工程师1 小时前
【Python实战】告别杂乱脚本!基于SOLID原则与策略模式的 PDF转Word 批量处理系统
python·设计模式·pdf·word·策略模式