设计模式之单例模式
Java 架构师深度解析单例模式
前言
在 Java 开发的世界里,单例模式(Singleton Pattern) 是所有设计模式中最简单、应用最广泛,也是面试必考的核心模式。作为一名 Java 架构师,我见过无数新手因为不懂单例导致生产环境出现资源泄漏、数据不一致、线程安全问题;也见过资深工程师用单例完美解决了全局配置、连接池、线程池等核心场景的架构难题。
单例模式是创建型设计模式的基石,它的核心思想只有一句话:保证一个类在整个 JVM 中仅有一个实例,并且提供一个全局访问该实例的入口。
这篇文章我会带你从 0 到 1吃透单例模式:从通俗定义、核心特征,到 7 种由浅入深的代码实现;从结合七大设计原则的架构思维,到 JDK、Spring、MyBatis 源码中的实战;再到单例的破坏、防护、最佳实践和面试高频考点。全文通俗易懂,无晦涩术语,适合所有 Java 开发者学习。
第一章 单例模式基础认知(小白必看)
1.1 什么是单例模式?通俗定义
我们用生活中的例子理解单例:
- 一个公司只有一个 CEO,所有人都找这同一个 CEO 汇报工作;
- 一个家庭只有一个户口本,所有家庭成员共用这一个证件;
- 一台电脑只有一个打印机,所有软件都调用这一个打印机打印文件。
映射到 Java 代码中:
一个类只能被创建一个对象,整个项目中所有地方使用的都是这同一个对象,不能 new 出第二个对象。这就是单例模式。
官方定义(GoF23 种设计模式):
单例模式确保一个类只有一个实例,并提供一个全局访问点来获取这个实例,同时禁止通过构造方法创建新实例。
1.2 单例模式的核心四大特征
所有合法的单例实现,都必须满足这 4 个特征,缺一不可:
-
私有构造方法(private constructor)
这是单例的核心防线!将类的构造方法用
private修饰,禁止外部通过new关键字创建对象,从根源上杜绝多实例。 -
私有静态实例变量(private static instance)
用
static修饰实例变量,保证实例属于类本身,而不是对象;private修饰保证外部无法直接修改。 -
全局公开的访问方法(public static getInstance ())
提供一个静态方法,作为外部获取单例对象的唯一入口。
-
唯一实例
无论调用多少次
getInstance(),返回的都是同一个对象,哈希码完全相同。
1.3 为什么必须使用单例模式?(3 大核心痛点)
如果不使用单例,随意创建对象,会引发 3 个致命问题:
1. 资源严重浪费
比如数据库连接池、线程池、日志对象:
- 每次创建连接池都要和数据库建立 TCP 连接,频繁创建会耗尽服务器资源;
- 线程池创建会占用内存和 CPU,多实例线程池会导致线程泛滥。
- 单例保证只创建一次,全局复用,极致节省资源。
2. 数据不一致
比如全局配置类、缓存类:
- 如果有两个配置实例,一个修改了配置,另一个感知不到,导致业务逻辑错乱;
- 多个缓存实例会导致缓存数据不统一,出现脏数据。
3. 全局状态失控
比如用户会话管理、分布式锁实例:
- 多实例会导致全局状态无法统一管理,引发并发安全问题。
1.4 单例模式的经典应用场景(企业级开发必备)
在 Java 企业开发中,90% 的全局组件都用单例实现:
- 配置管理类:项目的全局配置(如数据库配置、Redis 配置);
- 资源池类:数据库连接池、线程池、HTTP 连接池;
- 工具类:日志工具、加密工具、文件上传工具;
- 缓存类:本地缓存、分布式缓存客户端;
- 框架核心类:Spring 的 Bean(默认单例)、MyBatis 的 SqlSessionFactory;
- 系统服务类:JDK 的
Runtime、Desktop类。
1.5 单例模式的设计初衷与本质
单例模式的本质:控制实例数量 + 全局共享。
它是为了解决全局唯一对象的创建和使用问题,是高内聚、低耦合的经典实践。
第二章 单例模式的 7 种实现方式(由浅入深・代码实战)
单例的实现分为两大类:
- 饿汉式(Eager Initialization):类加载时就创建实例,线程安全,无延迟加载;
- 懒汉式(Lazy Initialization):第一次使用时才创建实例,支持延迟加载,需要处理线程安全。
我会从最简单到最优,逐一讲解 7 种实现,附带完整代码、优缺点、适用场景。
2.1 饿汉式(静态常量)- 最简单、最基础
核心思想
类加载的时候就直接创建实例,天生线程安全(JVM 类加载机制保证线程安全),代码极简。
代码实战
/**
* 饿汉式单例(静态常量)- 最简单实现
*/
public class Singleton1 {
// 1. 私有静态常量实例:类加载时就创建
private static final Singleton1 INSTANCE = new Singleton1();
// 2. 私有构造方法:禁止外部new
private Singleton1() {}
// 3. 全局公开访问方法
public static Singleton1 getInstance() {
return INSTANCE;
}
}
测试代码
public class SingletonTest {
public static void main(String[] args) {
Singleton1 s1 = Singleton1.getInstance();
Singleton1 s2 = Singleton1.getInstance();
// 输出true,说明是同一个对象
System.out.println(s1 == s2);
// 输出相同的哈希码
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
优点
- 代码最简单,无脑上手;
- JVM 类加载保证线程安全,无并发问题;
- 无锁,执行效率极高。
缺点
- 不支持延迟加载:即使项目不用这个实例,也会在类加载时创建,浪费内存;
- 如果实例初始化复杂,会拖慢类加载速度。
适用场景
实例小、初始化简单、全局必定会使用的场景(比如工具类)。
2.2 饿汉式(静态代码块)- 变种,适合复杂初始化
核心思想
和静态常量饿汉式完全一致,只是把实例创建放在静态代码块中,适合需要复杂初始化逻辑的场景(比如读取配置文件、加载资源)。
代码实战
/**
* 饿汉式单例(静态代码块)- 适合复杂初始化
*/
public class Singleton2 {
// 1. 私有静态实例
private static Singleton2 instance;
// 2. 私有构造
private Singleton2() {}
// 3. 静态代码块:类加载时执行,创建实例
static {
// 这里可以写复杂初始化逻辑:读取配置、加载资源
instance = new Singleton2();
}
// 4. 全局访问方法
public static Singleton2 getInstance() {
return instance;
}
}
优缺点 / 适用场景
和静态常量饿汉式完全相同,仅多了复杂初始化的能力。
2.3 懒汉式(线程不安全)- 延迟加载,入门级
核心思想
按需加载:第一次调用getInstance()时才创建实例,解决了饿汉式内存浪费的问题。
⚠️ 致命问题:多线程环境下完全不安全,会创建多个实例!
代码实战
/**
* 懒汉式单例(线程不安全)- 禁止生产使用!
*/
public class Singleton3 {
// 1. 私有静态实例:先不初始化
private static Singleton3 instance;
// 2. 私有构造
private Singleton3() {}
// 3. 全局访问方法:第一次调用时创建
public static Singleton3 getInstance() {
// 如果实例为空,才创建
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
问题分析
多线程下,线程 A 和线程 B 同时判断instance == null,都会进入创建逻辑,最终生成两个不同的对象,破坏单例。
优点
支持延迟加载,节省内存。
缺点
线程不安全,绝对不能用于生产环境。
适用场景
仅用于单线程测试环境,无任何实际价值。
2.4 懒汉式(线程安全,同步方法)- 解决安全,性能差
核心思想
在getInstance()方法上添加synchronized关键字,保证多线程下只有一个线程能进入方法,解决线程安全问题。
⚠️ 缺点:每次获取实例都要加锁,性能极差!
代码实战
/**
* 懒汉式单例(同步方法)- 线程安全,但性能低
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {}
// 添加synchronized锁,保证线程安全
public static synchronized Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
优缺点
- 优点:线程安全、支持延迟加载;
- 缺点:锁粒度太大,高并发下所有线程都要排队获取实例,性能暴跌 90% 以上。
适用场景
低并发、对性能无要求的场景,企业开发极少使用。
2.5 双重校验锁(DCL)- 高性能线程安全,主流
核心思想
Double-Check Locking,双重校验锁:
- 第一次校验:无锁判断实例是否存在,避免每次加锁;
- 加锁:只有实例为空时才加锁创建;
- 第二次校验:防止多线程重复创建;
- volatile 关键字:禁止指令重排,保证线程安全。
这是高性能 + 延迟加载 + 线程安全的平衡方案,是早年企业开发的主流。
代码实战
/**
* 双重校验锁(DCL)单例 - 高性能线程安全
*/
public class Singleton5 {
// 1. volatile关键字:禁止指令重排,核心!
private static volatile Singleton5 instance;
private Singleton5() {}
public static Singleton5 getInstance() {
// 第一次校验:无锁,提高效率
if (instance == null) {
// 加锁:类锁
synchronized (Singleton5.class) {
// 第二次校验:防止多线程重复创建
if (instance == null) {
instance = new Singleton5();
}
}
}
return instance;
}
}
关键知识点:为什么必须加 volatile?
instance = new Singleton5() 不是原子操作,JVM 会分为 3 步:
- 分配内存空间;
- 初始化对象;
- 将 instance 指向内存空间。
JVM 会指令重排,可能执行顺序变成 1→3→2。
多线程下,线程 A 执行到 3,线程 B 判断instance != null,直接返回未初始化的对象,引发空指针异常。
volatile禁止指令重排,保证执行顺序 1→2→3,彻底解决问题。
优缺点
- 优点:线程安全、延迟加载、高性能;
- 缺点:代码稍复杂,存在指令重排风险(必须加 volatile)。
适用场景
高并发、需要延迟加载的场景。
2.6 静态内部类 - 简洁优雅,天生线程安全
核心思想
利用JVM 类加载机制实现线程安全和延迟加载:
- 外部类加载时,静态内部类不会被加载;
- 第一次调用
getInstance()时,静态内部类才会被加载,创建实例; - JVM 保证类加载的线程安全性,无需加锁。
这是极简、高性能、线程安全的完美方案。
代码实战
/**
* 静态内部类单例 - 优雅、天生线程安全
*/
public class Singleton6 {
// 私有构造
private Singleton6() {}
// 1. 静态内部类:外部类加载时,内部类不会加载
private static class SingletonHolder {
// 2. 内部类中创建实例:JVM保证线程安全
private static final Singleton6 INSTANCE = new Singleton6();
}
// 3. 全局访问方法
public static Singleton6 getInstance() {
// 调用时才加载内部类,创建实例
return SingletonHolder.INSTANCE;
}
}
优缺点
- 优点:线程安全、延迟加载、无锁高性能、代码简洁;
- 缺点:无法传递参数初始化实例。
适用场景
绝大多数企业级场景,推荐优先使用。
2.7 枚举单例 - 业界公认最优,防破坏
核心思想
《Effective Java》作者 Josh Bloch 推荐:枚举单例是单例模式的最佳实现。
枚举天生:
- 只有一个实例;
- JVM 保证线程安全;
- 防止反射、序列化破坏单例(其他实现都做不到);
- 代码极简。
代码实战
/**
* 枚举单例 - 业界公认最优实现
*/
public enum Singleton7 {
// 唯一实例
INSTANCE;
// 单例的业务方法
public void doSomething() {
System.out.println("枚举单例执行业务逻辑");
}
}
测试代码
public class SingletonTest {
public static void main(String[] args) {
Singleton7 s1 = Singleton7.INSTANCE;
Singleton7 s2 = Singleton7.INSTANCE;
System.out.println(s1 == s2); // true
s1.doSomething();
}
}
优缺点
- 优点:线程安全、防反射 / 序列化破坏、代码极简、性能最优;
- 缺点:不支持延迟加载(类加载时创建)。
适用场景
所有企业级生产环境,架构师首选!
2.8 扩展:容器式单例、ThreadLocal 单例
1. 容器式单例
适用于管理大量单例对象的场景(Spring 底层就是用容器单例):
import java.util.HashMap;
import java.util.Map;
/**
* 容器式单例:统一管理多个单例
*/
public class ContainerSingleton {
private static Map<String, Object> singletonMap = new HashMap<>();
private ContainerSingleton() {}
// 注册单例
public static void registerInstance(String key, Object instance) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
// 获取单例
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
2. ThreadLocal 单例
线程级单例:每个线程拥有一个独立实例,线程之间隔离:
/**
* ThreadLocal单例:线程隔离的单例
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> THREAD_LOCAL =
ThreadLocal.withInitial(ThreadLocalSingleton::new);
private ThreadLocalSingleton() {}
public static ThreadLocalSingleton getInstance() {
return THREAD_LOCAL.get();
}
}
2.9 7 种实现方式对比总结(选型表)
| 实现方式 | 线程安全 | 延迟加载 | 性能 | 防破坏 | 推荐指数 |
|---|---|---|---|---|---|
| 饿汉式(常量) | ✅ | ❌ | 高 | ❌ | ⭐⭐⭐ |
| 饿汉式(静态块) | ✅ | ❌ | 高 | ❌ | ⭐⭐⭐ |
| 懒汉式(不安全) | ❌ | ✅ | 高 | ❌ | ❌ |
| 懒汉式(同步方法) | ✅ | ✅ | 极低 | ❌ | ⭐ |
| 双重校验锁 | ✅ | ✅ | 高 | ❌ | ⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | 高 | ❌ | ⭐⭐⭐⭐⭐ |
| 枚举单例 | ✅ | ❌ | 高 | ✅ | ⭐⭐⭐⭐⭐⭐ |
第三章 单例模式与七大设计原则(架构师核心思维)
七大设计原则是架构设计的基石,单例模式作为最常用的设计模式,完美体现了这些原则的遵循与取舍。
我会逐一结合单例模式,用通俗的语言讲解,让你真正理解架构设计的本质。
3.1 单一职责原则(SRP)
原则定义
一个类只负责一件事,只有一个引起它变化的原因。
单例模式的体现
✅ 完全遵循:
单例类的核心职责只有两个:
- 创建自己的唯一实例;
- 提供全局访问入口。
它不参与业务逻辑,不处理数据,只负责实例的创建和管理,完美符合单一职责。
❌ 反例:
如果把单例类写成 “万能类”,既管理实例,又做数据库操作、日志打印、业务逻辑,就违背了单一职责。
3.2 开闭原则(OCP)
原则定义
对扩展开放,对修改关闭。不修改原有代码,通过扩展实现新功能。
单例模式的体现
✅ 基础遵循:
单例的实例管理逻辑固定,无需修改;可以通过继承、实现接口扩展单例的功能,不破坏原有代码。
❌ 局限性:
- 饿汉式、静态内部类单例,实例创建逻辑写死,扩展需要修改代码;
- 枚举单例无法继承,扩展性最差。
架构优化
面向接口编程,将单例的业务逻辑抽象为接口,通过实现接口扩展功能,符合开闭原则。
3.3 里氏替换原则(LSP)
原则定义
子类可以完全替换父类,程序逻辑不变。父类能出现的地方,子类都能替换。
单例模式的体现
✅ 完全遵循:
- 单例类可以作为父类,子类继承后,依然可以作为单例使用;
- 只要子类不破坏单例的核心特征(唯一实例),就可以无缝替换父类。
⚠️ 注意:
单例不推荐滥用继承,否则会破坏实例唯一性,违背里氏替换原则。
3.4 接口隔离原则(ISP)
原则定义
使用多个专门的接口,而不是一个臃肿的总接口。客户端不依赖不需要的方法。
单例模式的体现
✅ 遵循则优,违背则劣:
- 好的设计:单例类实现细粒度接口,只暴露需要的方法;
- 坏的设计:单例类实现臃肿接口,包含大量无用方法,客户端被迫依赖不需要的功能。
示例
// 好的设计:细粒度接口
interface ConfigLoader {
void loadConfig();
}
// 单例实现专用接口,符合接口隔离
public class ConfigSingleton implements ConfigLoader {}
3.5 依赖倒置原则(DIP)
原则定义
依赖抽象,不依赖具体。高层模块不依赖低层模块,二者都依赖抽象。
单例模式的体现
✅ 架构级遵循:
企业开发中,我们不直接依赖单例类,而是依赖单例实现的接口:
// 抽象接口
interface CacheService {
Object get(String key);
}
// 具体单例
public class RedisSingleton implements CacheService {}
// 高层依赖抽象,不依赖具体单例
public class UserService {
private CacheService cacheService;
// 依赖注入接口,符合依赖倒置
public UserService(CacheService cacheService) {
this.cacheService = cacheService;
}
}
这是 Spring 框架的核心设计思想。
3.6 迪米特法则(LOD)- 最少知道原则
原则定义
一个类只和直接朋友通信,不和陌生人通信。最少暴露内部细节,降低耦合。
单例模式的体现
✅ 完美遵循:
- 单例类私有构造,不暴露内部创建逻辑;
- 只暴露一个
getInstance()方法,外部无需知道单例的创建细节; - 外部只和
getInstance()交互,耦合度极低。
3.7 合成复用原则(CRP)
原则定义
优先使用组合 / 聚合,而不是继承实现复用。
单例模式的体现
✅ 严格遵循:
单例模式禁止滥用继承,因为继承会破坏实例唯一性;
推荐使用组合:将单例对象注入到其他类中使用,实现复用。
// 组合复用:将单例注入到业务类
public class OrderService {
// 组合单例对象
private final LogSingleton log = LogSingleton.getInstance();
}
3.8 总结:单例模式对七大原则的遵循与违背
| 原则 | 遵循情况 | 核心原因 |
|---|---|---|
| 单一职责 | ✅ 完全遵循 | 只负责实例创建与管理 |
| 开闭原则 | ⚠️ 部分遵循 | 枚举单例扩展性差 |
| 里氏替换 | ✅ 完全遵循 | 子类可无缝替换父类 |
| 接口隔离 | ✅ 可完全遵循 | 依赖细粒度接口即可 |
| 依赖倒置 | ✅ 架构级遵循 | 面向接口编程,依赖抽象 |
| 迪米特法则 | ✅ 完美遵循 | 最小暴露,低耦合 |
| 合成复用 | ✅ 严格遵循 | 优先组合,禁止继承 |
核心结论:单例模式是最符合七大设计原则的设计模式之一,只要合理设计,就能实现高内聚、低耦合的架构。
第四章 企业级源码中的单例模式实战(吃透底层)
作为架构师,不仅要会写单例,更要读懂开源框架、JDK 源码中的单例设计,这是进阶的关键。
4.1 JDK 源码中的单例模式:Runtime 类
java.lang.Runtime是 JDK 自带的经典饿汉式单例,每个 JVM 进程只有一个 Runtime 实例,用于获取系统信息、执行系统命令。
源码截取
public class Runtime {
// 饿汉式单例:静态常量
private static Runtime currentRuntime = new Runtime();
// 全局访问方法
public static Runtime getRuntime() {
return currentRuntime;
}
// 私有构造:禁止外部创建
private Runtime() {}
}
使用方式
public class RuntimeTest {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
// 获取JVM内存信息
System.out.println("最大内存:" + runtime.maxMemory());
}
}
4.2 Spring 框架中的单例模式:Bean 单例
Spring 的 Bean默认作用域是 singleton(单例),是企业开发中最常用的单例实现。
核心原理
Spring 用容器式单例管理 Bean:
- 通过
SingletonBeanRegistry接口定义单例注册、获取规范; - 用
DefaultSingletonBeanRegistry实现,底层用Map<String, Object> singletonObjects缓存所有单例 Bean; - 第一次获取 Bean 时创建,后续直接从缓存获取。
核心源码(简化)
public class DefaultSingletonBeanRegistry {
// 单例缓存池:key=beanName,value=bean实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 获取单例Bean
public Object getSingleton(String beanName) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 创建Bean,放入缓存
singletonObject = createBean(beanName);
this.singletonObjects.put(beanName, singletonObject);
}
return singletonObject;
}
}
4.3 MyBatis 框架中的单例模式
MyBatis 的核心组件SqlSessionFactory、ErrorContext都是单例:
SqlSessionFactory:全局唯一,负责创建 SqlSession;ErrorContext:线程级单例,用 ThreadLocal 实现。
4.4 其他中间件的单例应用
- Tomcat:全局配置类、线程池都是单例;
- Redis 客户端:JedisPool 连接池是单例;
- Dubbo:注册中心、协议注册器都是单例。
第五章 单例模式的破坏与防护(高级必学)
除了枚举单例,其他所有单例实现都可以被破坏!
作为架构师,必须掌握单例的4 种破坏方式和防护手段。
5.1 反射破坏单例:原理 + 代码 + 防护
破坏原理
反射可以无视私有构造方法,直接调用newInstance()创建新对象。
破坏代码
public class ReflectBreak {
public static void main(String[] args) throws Exception {
// 获取单例
Singleton6 s1 = Singleton6.getInstance();
// 反射获取构造方法
Constructor<Singleton6> constructor = Singleton6.class.getDeclaredConstructor();
// 无视私有构造
constructor.setAccessible(true);
// 创建新对象
Singleton6 s2 = constructor.newInstance();
// 输出false,单例被破坏
System.out.println(s1 == s2);
}
}
防护方案
在私有构造方法中判断实例是否已存在,存在则抛出异常:
private Singleton6() {
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("禁止反射破坏单例!");
}
}
5.2 序列化破坏单例:原理 + 代码 + 防护
破坏原理
序列化会把对象写入磁盘,反序列化时会创建新对象,破坏单例。
防护方案
单例类实现Serializable,重写readResolve()方法,直接返回单例实例:
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
5.3 克隆破坏单例:原理 + 代码 + 防护
破坏原理
clone()方法会创建一个新的对象,不走构造方法,破坏单例。
防护方案
- 单例类不实现 Cloneable 接口;
- 若必须实现,重写
clone()方法,抛出异常:
@Override
protected Object clone() throws CloneNotSupportedException {
throw new RuntimeException("禁止克隆破坏单例!");
}
5.4 多类加载器破坏单例
破坏原理
不同类加载器加载同一个类,会创建多个 Class 对象,从而生成多个单例实例。
防护方案
指定统一的类加载器,保证类只被加载一次。
第六章 单例模式的优缺点与适用场景(精准选型)
6.1 单例模式的核心优点
- 资源最优利用:只创建一个实例,节省内存、CPU、连接资源;
- 全局状态统一:避免多实例导致的数据不一致;
- 全局访问方便:无需传递对象,随处调用;
- 线程安全可控:合理实现可保证高并发下的安全。
6.2 单例模式的核心缺点
- 扩展性差:单例类很难扩展,违背开闭原则;
- 测试困难:单例的全局状态会导致单元测试相互干扰;
- 隐藏依赖:单例无需注入,容易隐藏类之间的依赖关系;
- 生命周期过长:单例伴随 JVM 全程,容易引发内存泄漏。
6.3 单例模式的适用场景
- 全局唯一的资源(连接池、线程池、配置);
- 无状态的工具类;
- 框架核心组件;
- 需要全局共享数据的场景。
6.4 单例模式的反适用场景
- 有状态的业务对象;
- 需要频繁创建销毁的对象;
- 需要多实例隔离的场景;
- 追求高扩展性的模块。
第七章 单例模式最佳实践(架构师经验)
7.1 企业开发选型建议
- 首选枚举单例:简单、安全、防破坏,适合绝大多数场景;
- 需要延迟加载选静态内部类:高性能、优雅;
- 高并发复杂场景选双重校验锁;
- 小实例、必用场景选饿汉式。
7.2 单例类的设计规范
- 构造方法必须私有,且添加防护逻辑;
- 禁止在单例中存储可变状态;
- 单例类保持精简,符合单一职责;
- 面向接口编程,依赖抽象不依赖具体。
7.3 单例与依赖注入(DI)结合
Spring 的 DI + 单例是最佳实践:
- 用 Spring 管理单例 Bean,无需手动写单例代码;
- 依赖注入解决单例的隐藏依赖问题;
- 方便单元测试替换单例实现。
第八章 单例模式高频面试题(通关必备)
8.1 基础面试题
- 什么是单例模式?核心特征是什么?
- 饿汉式和懒汉式的区别?
- 单例模式的应用场景?
8.2 中级面试题
- 双重校验锁为什么必须加 volatile?
- 静态内部类单例的原理?
- 如何防止反射破坏单例?
8.3 高级面试题
- 为什么枚举单例是最优实现?
- Spring 单例 Bean 的线程安全问题?
- 多线程、多类加载器下如何保证单例?
结语
单例模式看似简单,实则蕴含了JVM 类加载、线程安全、设计原则、架构设计等核心知识。作为 Java 开发者,单例是你必须吃透的第一个设计模式,也是通往架构师之路的基石。
核心总结:
- 单例的本质:唯一实例 + 全局访问;
- 最优实现:枚举单例;
- 架构核心:遵循七大设计原则,面向接口编程;
- 企业实践:结合 Spring 依赖注入,杜绝手动写单例。
希望这篇万字长文,能让你真正掌握单例模式,在开发、面试、架构设计中得心应手!
浙公网安备 33010602011771号