1. 单例模式:如何用 volatile 和 synchronized 实现?(双重校验锁)
这是面试中最标准的单例模式写法(DCL - Double Checked Locking)。
代码逻辑:
public class Singleton {
// 1. 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;
}
}面试回答重点:
-
为什么要有两次
if (instance == null)?-
第一次是为了性能(一旦实例创建好,后续线程就不用排队加锁了)。
-
第二次是为了安全(防止两个线程同时通过了第一层检查,一个在等待锁,另一个创建完释放锁后,等待的那个进去又创建了一次)。
-
-
为什么要用
volatile?(核心考点)-
instance = new Singleton()这行代码在 JVM 里其实分三步:-
分配内存空间。
-
初始化对象。
-
将引用指向内存地址。
-
-
如果不加
volatile,CPU 可能会指令重排(变成 1 → 3 → 2)。 -
后果: 线程 A 执行了 1 和 3(此时
instance已经不为 null 了,但对象还没初始化),线程 B 进来发现instance != null,直接拿去用了,结果用到的是个半成品,导致报错。
-
2. 代理模式 vs 适配器模式的区别?
这两个模式结构很像(都是包一层),但意图完全不同。
-
适配器模式 (Adapter Pattern):
-
核心意图: “兼容”。把一个类的接口变换成客户端所期待的另一种接口。
-
比喻: 手机转接头(把 Type-C 转成 3.5mm 耳机孔)。
-
特点: 改变了原来的接口。
-
-
代理模式 (Proxy Pattern):
-
核心意图: “控制”与“增强”。在不改变接口的前提下,对原对象的功能进行扩展(如加日志、加权限控制)。
-
比喻: 明星的经纪人。你要找明星(原对象)谈生意,必须先经过经纪人(代理),经纪人负责谈价钱(增强逻辑),谈好了再让明星签字。
-
特点: 接口保持一致。
-
总结话术: “适配器模式是为了解决接口不匹配的问题(比如改电压);代理模式是为了在不改变接口的前提下增强功能(比如 AOP 切面)。”
3. 责任链模式 (Chain of Responsibility) 的应用场景?
核心概念: 将请求的发送者和接收者解耦。请求沿着一条链传递,直到有一个对象处理它为止,或者每个对象都处理一部分。
面试常考应用场景:
-
Java Web 过滤器链 (Filter Chain): 请求到达 Servlet 之前,先经过编码过滤、登录检查过滤等。
-
Spring 拦截器 (Interceptor): Controller 执行前后的处理。
-
日志级别 (Logger):
debug→info→error。如果不满足级别就传给下一个,或者冒泡处理。 -
风控系统/审批流程: 比如请假审批:组长 → 经理 → 总监。金额小的时候组长批完就结束,金额大了继续往下传。
| 模式 | 核心关键词 | 典型用途 |
|---|---|---|
| 单例模式 | volatile, 双重检查 | 保证全局只有一个实例 (如数据库连接池) |
| 适配器模式 | 兼容 | 救火代码,连接旧接口 |
| 代理模式 | 增强 | Spring AOP, RPC, 日志, 事务 |
| 责任链模式 | 链式处理 | Filter, 拦截器, 审批流 |