Skip to content

创建型设计模式深度解析

面试官:单例模式你了解吗?有几种写法?

:有多种写法,常用的有饿汉式、懒汉式、双检锁(DCL)、静态内部类和枚举。

面试官:DCL 双检锁里,volatile 为什么不能去掉?

这是单例模式面试的终极追问。能解释清楚指令重排序问题的候选人,才能证明真正理解了并发编程的底层原理。


Q1:单例模式有几种写法?各自的优缺点是什么?必考

Section titled “Q1:单例模式有几种写法?各自的优缺点是什么?”

核心要求

  1. 私有构造器(防止外部 new)
  2. 静态实例变量
  3. 公共静态获取方法

五种实现方式对比

实现方式线程安全懒加载防止反射破坏防止序列化破坏推荐度
饿汉式⭐⭐⭐
懒汉式(同步方法)
DCL 双检锁⭐⭐⭐
静态内部类⭐⭐⭐⭐
枚举⭐⭐⭐⭐⭐

实现代码详解

// ==================== 1. 饿汉式 ====================
public class Singleton1 {
// 类加载时就创建,JVM 保证线程安全
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
// 优点:简单,线程安全
// 缺点:类加载就创建,可能浪费内存(实际很少成为问题)
// ==================== 2. 懒汉式(线程不安全) ====================
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
// ❌ 线程不安全!多线程可能创建多个实例
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
// ==================== 3. DCL 双检锁 ====================
public class Singleton3 {
// volatile 不可省略!防止指令重排序
private static volatile Singleton3 instance;
private Singleton3() {}
public static Singleton3 getInstance() {
if (instance == null) { // 第一次检查:避免不必要的锁竞争
synchronized (Singleton3.class) {
if (instance == null) { // 第二次检查:防止重复创建
instance = new Singleton3();
}
}
}
return instance;
}
}
// 优点:懒加载 + 线程安全 + 高性能(锁粒度小)
// 缺点:volatile 有性能开销(现代 JVM 很小)
// ==================== 4. 静态内部类 ====================
public class Singleton4 {
private Singleton4() {}
// 静态内部类不会在 Singleton4 加载时初始化
private static class Holder {
private static final Singleton4 INSTANCE = new Singleton4();
}
public static Singleton4 getInstance() {
return Holder.INSTANCE; // 第一次调用时才加载 Holder
}
}
// 优点:懒加载 + 线程安全(JVM 保证)+ 无锁
// 缺点:无法传参初始化
// ==================== 5. 枚举(最佳实践) ====================
public enum Singleton5 {
INSTANCE;
public void doSomething() { }
}
// 调用:Singleton5.INSTANCE.doSomething()
// 优点:线程安全 + 防止反射 + 防止序列化
// 缺点:无法懒加载

Q2:DCL 中 volatile 为什么不能去掉?必考

Section titled “Q2:DCL 中 volatile 为什么不能去掉?”

核心原因:指令重排序导致半初始化状态

对象创建的三个步骤

instance = new Singleton() 在字节码层面分为三步:
步骤 1: memory = allocate() // 分配内存空间
步骤 2: ctorInstance(memory) // 初始化对象(调用构造方法)
步骤 3: instance = memory // 将引用指向内存地址
问题:步骤 2 和 3 可能重排序为 1 → 3 → 2

重排序导致的问题

┌────────────────────────────────────────────────┐
│ 无 volatile 时的并发问题 │
├────────────────────────────────────────────────┤
│ │
│ 线程 A 线程 B │
│ │ │ │
│ ├─ 1. 分配内存 │ │
│ │ │ │
│ ├─ 3. instance = memory ──────┼─ 第一次检查 │
│ │ (instance != null) │ instance │
│ │ │ != null │
│ │ │ ↓ │
│ │ │ 返回实例 │
│ │ │ (使用半 │
│ │ │ 初始化对象 │
│ ├─ 2. 初始化对象 │ → 错误!) │
│ │ │ │
│ │
│ 线程 B 使用了未初始化完成的对象! │
│ │
└────────────────────────────────────────────────┘

volatile 的作用

// volatile 通过内存屏障禁止指令重排序
private static volatile Singleton instance;
// 编译后插入内存屏障:
// 1. 分配内存
// 2. 初始化对象
// StoreStore 屏障(禁止上方指令重排到下方)
// 3. instance = memory
// StoreLoad 屏障(禁止下方指令重排到上方)
// 保证:instance 非空时,对象一定已初始化完成

性能影响

操作耗时(纳秒)说明
普通读1无屏障
volatile 读10LoadLoad + LoadStore 屏障
普通写1无屏障
volatile 写20StoreStore + StoreLoad 屏障

Q3:如何防止反射和序列化破坏单例?高频

Section titled “Q3:如何防止反射和序列化破坏单例?”

反射破坏单例

// 反射可以破坏私有构造器
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // 暴力反射
Singleton instance1 = constructor.newInstance();
Singleton instance2 = constructor.newInstance();
System.out.println(instance1 == instance2); // false!破坏了单例
// 防止方法:构造器中检查
private Singleton() {
if (Holder.INSTANCE != null) {
throw new RuntimeException("单例已存在,禁止反射创建");
}
}

序列化破坏单例

// 序列化 + 反序列化会创建新对象
Singleton instance1 = Singleton.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance1);
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()));
Singleton instance2 = (Singleton) ois.readObject();
System.out.println(instance1 == instance2); // false!破坏了单例

防止方法对比

方法防止反射防止序列化实现复杂度
构造器检查
readResolve 方法
枚举极低
// 方法 1:readResolve 防止序列化破坏
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
// 反序列化时,JVM 会调用此方法返回指定对象
private Object readResolve() {
return getInstance(); // 返回单例实例,而非创建新对象
}
}
// 方法 2:枚举(推荐)
public enum Singleton {
INSTANCE;
}
// JDK 保证:枚举的反射会抛异常,序列化/反序列化返回同一实例

枚举防止反射的原理

// Constructor.newInstance 源码片段
if ((clazz.getModifiers() & Modifier.ENUM) != 0) {
throw new IllegalArgumentException("Cannot reflectively create enum objects");
}
// JDK 在构造器层面禁止了枚举的反射创建

Q4:单例模式在 Spring 中的应用?高频

Section titled “Q4:单例模式在 Spring 中的应用?”

Spring Bean 的单例实现

// Spring 的单例是"容器级单例",不是 JVM 级单例
public class DefaultSingletonBeanRegistry {
// 单例缓存:Map 存储
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 获取单例 Bean
public Object getSingleton(String beanName) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
synchronized (this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = createBean(beanName); // 创建 Bean
this.singletonObjects.put(beanName, singletonObject);
}
}
}
return singletonObject;
}
}

Spring 单例 vs 传统单例

对比维度传统单例Spring 单例
作用范围JVM 级别(ClassLoader)容器级别(ApplicationContext)
实现方式静态变量 + 私有构造器Map 缓存 + 依赖注入
多实例不支持支持(prototype、request 等 scope)
生命周期管理有(初始化、销毁回调)
┌────────────────────────────────────────────────┐
│ Spring 单例的作用域 │
├────────────────────────────────────────────────┤
│ │
│ JVM │
│ ├── ClassLoader 1 │
│ │ └── ApplicationContext A │
│ │ └── Singleton Bean X │
│ │ │
│ └── ClassLoader 2 │
│ └── ApplicationContext B │
│ └── Singleton Bean X │
│ │
│ 结论: │
│ - 一个 ApplicationContext 中,Bean X 是单例 │
│ - 多个 ApplicationContext,可能有多个实例 │
│ - 传统单例是 ClassLoader 级别 │
│ │
└────────────────────────────────────────────────┘

Q5:简单工厂、工厂方法、抽象工厂的区别?必考

Section titled “Q5:简单工厂、工厂方法、抽象工厂的区别?”

三种工厂模式对比

┌────────────────────────────────────────────────┐
│ 工厂模式演进路径 │
├────────────────────────────────────────────────┤
│ │
│ 简单工厂 │
│ └── 一个工厂,switch 分发产品 │
│ └── 问题:新增产品要修改工厂(违反OCP) │
│ │
│ 工厂方法 │
│ └── 工厂接口 + 多个工厂实现 │
│ └── 一个工厂只生产一种产品 │
│ └── 符合 OCP,但类数量爆炸 │
│ │
│ 抽象工厂 │
│ └── 工厂接口 + 多个工厂实现 │
│ └── 一个工厂生产一族产品 │
│ └── 解决产品族问题 │
│ │
└────────────────────────────────────────────────┘

代码实现对比

// ==================== 1. 简单工厂 ====================
public class SimpleFactory {
public static Product create(String type) {
switch (type) {
case "A": return new ProductA();
case "B": return new ProductB();
default: throw new IllegalArgumentException("未知产品类型");
}
}
}
// 问题:新增产品必须修改工厂类(违反开闭原则)
// ==================== 2. 工厂方法 ====================
public interface ProductFactory {
Product create(); // 每个工厂只生产一种产品
}
public class ProductAFactory implements ProductFactory {
@Override
public Product create() {
return new ProductA();
}
}
public class ProductBFactory implements ProductFactory {
@Override
public Product create() {
return new ProductB();
}
}
// 新增产品:新增工厂类,不修改已有代码(符合 OCP)
// ==================== 3. 抽象工厂(产品族) ====================
// 场景:跨平台 UI 组件(Windows/Mac 风格)
public interface UIFactory {
Button createButton(); // 产品1:按钮
Dialog createDialog(); // 产品2:对话框
}
public class WindowsUIFactory implements UIFactory {
@Override
public Button createButton() {
return new WindowsButton(); // Windows 风格按钮
}
@Override
public Dialog createDialog() {
return new WindowsDialog(); // Windows 风格对话框
}
}
public class MacUIFactory implements UIFactory {
@Override
public Button createButton() {
return new MacButton(); // Mac 风格按钮
}
@Override
public Dialog createDialog() {
return new MacDialog(); // Mac 风格对话框
}
}

产品族示意图

┌────────────────────────────────────────────────┐
│ 抽象工厂:产品族概念 │
├────────────────────────────────────────────────┤
│ │
│ UIFactory(抽象工厂) │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ │ │
│ WindowsUIFactory MacUIFactory │
│ │ │ │ │ │
│ │ │ │ │ │
│ Button Dialog Button Dialog │
│ │ │ │ │ │
│ Windows Windows Mac Mac │
│ Button Dialog Button Dialog │
│ │
│ 产品族:Windows 风格 / Mac 风格 │
│ 产品等级:Button / Dialog │
│ │
└────────────────────────────────────────────────┘

Q6:Spring 中有哪些工厂模式的应用?高频

Section titled “Q6:Spring 中有哪些工厂模式的应用?”

Spring 的三大工厂

// ==================== 1. BeanFactory(工厂方法) ====================
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
// Spring 容器的顶层接口,生产 Bean
}
// 实现类
public class DefaultListableBeanFactory implements BeanFactory {
// 生产 Bean 的逻辑
}
// ==================== 2. FactoryBean(自定义工厂) ====================
public interface FactoryBean<T> {
T getObject() throws Exception; // 生产对象
Class<?> getObjectType(); // 对象类型
default boolean isSingleton() { // 是否单例
return true;
}
}
// 自定义 FactoryBean
@Component
public class SqlSessionFactoryBean implements FactoryBean<SqlSession> {
@Override
public SqlSession getObject() {
// 自定义创建逻辑
return new SqlSession(configuration);
}
@Override
public Class<?> getObjectType() {
return SqlSession.class;
}
}
// ==================== 3. ApplicationContext(增强工厂) ====================
public interface ApplicationContext extends BeanFactory {
// 在 BeanFactory 基础上增加了:
// - 国际化支持
// - 事件发布
// - 资源加载
// - AOP 支持
}

FactoryBean vs BeanFactory

对比维度BeanFactoryFactoryBean
作用Spring 容器的顶层接口用户自定义 Bean 创建逻辑
谁来用框架开发者
生产方式反射 + 依赖注入用户自定义
典型应用所有 Bean 的生产MyBatis SqlSessionFactory

MyBatis 的 FactoryBean 应用

// MyBatis 整合 Spring 的核心
public class SqlSessionFactoryBean implements FactoryBean<SqlSession>, InitializingBean {
private DataSource dataSource;
private Configuration configuration;
@Override
public SqlSession getObject() throws Exception {
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
.build(configuration);
return factory.openSession();
}
@Override
public Class<?> getObjectType() {
return SqlSession.class;
}
}
// Spring 配置
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean;
}

Q7:建造者模式解决什么问题?什么时候用?高频

Section titled “Q7:建造者模式解决什么问题?什么时候用?”

适用场景

  1. 构建参数很多的复杂对象
  2. 参数有必填和可选之分
  3. 需要分步骤构建
  4. 构建过程需要校验

问题:望远镜构造函数

// ❌ 问题:参数过多,难以理解
Pizza pizza = new Pizza("large", "thin", true, false, true, "tomato", "mozzarella");
// ↑ ↑ ↑ ↑ ↑ ↑ ↑
// size crust cheese pepper ham sauce1 sauce2
// 问题:参数顺序易错,可选参数不清晰
// ❌ 重载构造函数:组合爆炸
public class Pizza {
public Pizza(String size) { }
public Pizza(String size, String crust) { }
public Pizza(String size, String crust, boolean cheese) { }
public Pizza(String size, String crust, boolean cheese, boolean pepper) { }
// 参数组合爆炸,维护困难
}

建造者模式解决方案

// ✅ 建造者模式:流式 API
public class Pizza {
private final String size; // 必填
private final String crust; // 必填
private final boolean cheese; // 可选
private final boolean pepper; // 可选
private final List<String> toppings; // 可选
// 私有构造器
private Pizza(Builder builder) {
this.size = builder.size;
this.crust = builder.crust;
this.cheese = builder.cheese;
this.pepper = builder.pepper;
this.toppings = builder.toppings;
}
// 建造者
public static class Builder {
private final String size; // 必填
private final String crust; // 必填
private boolean cheese = false; // 默认值
private boolean pepper = false;
private List<String> toppings = new ArrayList<>();
public Builder(String size, String crust) {
this.size = size;
this.crust = crust;
}
public Builder cheese(boolean cheese) {
this.cheese = cheese;
return this; // 返回 this,支持链式调用
}
public Builder pepper(boolean pepper) {
this.pepper = pepper;
return this;
}
public Builder topping(String topping) {
this.toppings.add(topping);
return this;
}
public Pizza build() {
// 校验逻辑
if (size == null || crust == null) {
throw new IllegalArgumentException("必填参数不能为空");
}
return new Pizza(this);
}
}
}
// 使用:清晰易读
Pizza pizza = new Pizza.Builder("large", "thin")
.cheese(true)
.pepper(false)
.topping("tomato")
.topping("mozzarella")
.build();

建造者模式 vs 工厂模式

对比维度工厂模式建造者模式
关注点创建对象的类型创建对象的过程
参数较少,简单较多,复杂
构建过程一步完成分步骤进行
返回时机立即返回对象最后调用 build()
典型应用Spring BeanFactoryStringBuilder

Spring/JDK 中的建造者

// 1. Lombok @Builder(最常用)
@Builder
public class User {
private String name;
private Integer age;
private String email;
}
User user = User.builder().name("张三").age(25).build();
// 2. StringBuilder
StringBuilder sb = new StringBuilder()
.append("Hello")
.append(" ")
.append("World");
String result = sb.toString();
// 3. UriComponentsBuilder(Spring)
URI uri = UriComponentsBuilder
.fromHttpUrl("https://api.example.com")
.path("/users")
.queryParam("page", 1)
.queryParam("size", 10)
.build()
.toUri();
// 4. HttpRequest.Builder(Java 11)
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();

Q8:原型模式是什么?深拷贝和浅拷贝的区别?中频

Section titled “Q8:原型模式是什么?深拷贝和浅拷贝的区别?”

定义:通过复制已有对象来创建新对象,而不是通过 new。

Java 中的实现

// 实现 Cloneable 接口
public class User implements Cloneable {
private String name;
private Address address; // 引用类型
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone(); // 浅拷贝
}
}
// 浅拷贝:只复制基本类型,引用类型仍指向原对象
User user1 = new User("张三", new Address("北京"));
User user2 = user1.clone();
user2.setName("李四"); // 不影响 user1
user2.getAddress().setCity("上海"); // 影响 user1!(address 是同一个对象)

深拷贝 vs 浅拷贝

┌────────────────────────────────────────────────┐
│ 浅拷贝 │
├────────────────────────────────────────────────┤
│ │
│ 原对象 拷贝对象 │
│ ┌─────┐ ┌─────┐ │
│ │name │ │name │ │
│ │ ↓ │ │ ↓ │ │
│ │张三 │ │李四 │ ← 独立副本 │
│ └─────┘ └─────┘ │
│ │
│ ┌─────┐ ┌─────┐ │
│ │address│──────────────→address│ │
│ │ ↓ │ │ ↓ │ │
│ │北京 │ │上海 │ ← 同一个对象!│
│ └─────┘ └─────┘ │
│ │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ 深拷贝 │
├────────────────────────────────────────────────┤
│ │
│ 原对象 拷贝对象 │
│ ┌─────┐ ┌─────┐ │
│ │name │ │name │ │
│ │ ↓ │ │ ↓ │ │
│ │张三 │ │李四 │ ← 独立副本 │
│ └─────┘ └─────┘ │
│ │
│ ┌─────┐ ┌─────┐ │
│ │address│ │address│ │
│ │ ↓ │ │ ↓ │ │
│ │北京 │ │北京 │ ← 独立副本! │
│ └─────┘ └─────┘ │
│ │
└────────────────────────────────────────────────┘

深拷贝实现方式

// 方式 1:手动深拷贝
@Override
protected User clone() throws CloneNotSupportedException {
User cloned = (User) super.clone();
cloned.address = address.clone(); // 递归拷贝引用对象
return cloned;
}
// 方式 2:序列化(推荐)
public User deepCopy() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()));
return (User) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 方式 3:JSON 序列化(最简单)
User copy = JSON.parseObject(JSON.toJSONString(user), User.class);

性能对比

方式性能实现复杂度适用场景
手动 clone最高高(需要递归)简单对象
Java 序列化复杂对象图
JSON 序列化极低不要求性能的场景

四种模式对比

模式核心问题关键特征Spring/JDK 应用
单例全局唯一实例私有构造器 + 静态方法Spring Bean 默认 scope
工厂对象创建逻辑复杂封装创建逻辑BeanFactory、FactoryBean
建造者多参数复杂对象流式 API + 分步构建StringBuilder、@Builder
原型避免重复创建相似对象克隆而非 newObject.clone()

选择决策树

┌────────────────────────────────────────────────┐
│ 创建型模式选择 │
├────────────────────────────────────────────────┤
│ │
│ 需要创建对象吗? │
│ └── 是 │
│ │ │
│ ├── 需要全局唯一实例? │
│ │ └── 是 → 单例模式 │
│ │ │
│ ├── 需要创建逻辑复杂? │
│ │ ├── 一族产品 → 抽象工厂 │
│ │ └── 一种产品 → 工厂方法 │
│ │ │
│ ├── 参数很多很复杂? │
│ │ └── 是 → 建造者模式 │
│ │ │
│ └── 已有相似对象? │
│ └── 是 → 原型模式 │
│ │
└────────────────────────────────────────────────┘

常见追问

  1. Q:单例模式的线程安全问题? A:饿汉式、静态内部类、枚举天然线程安全;DCL 需要 volatile;懒汉式需要同步方法。

  2. Q:工厂模式增加了类的数量,值得吗? A:值得。类数量的增加换来的是开闭原则的遵守,新增产品不需要修改已有代码,降低维护成本。

  3. Q:建造者模式和构造器注入如何选择? A:参数少且简单用构造器注入;参数多或有复杂构建逻辑用建造者。

  4. Q:原型模式的实际应用场景? A:对象创建成本高(数据库查询、网络请求)、需要保留历史版本、避免重复初始化。