直接用 synchronized 方法写单例不行,因为每次调用 getInstance() 都需加锁,即使实例已创建也会导致线程阻塞、性能下降;正确解法是 DCL(双重检查锁定)+ volatile,确保高并发下线程安全且高效。

为什么直接用 synchronized 方法写单例不行
因为每次 getInstance() 都要加锁,性能差——哪怕实例已经创建好了,还要排队等锁。面试官想看的不是“能跑”,而是“高并发下既安全又高效”的解法。
常见错误现象:
- 没加 volatile,导致指令重排序,其他线程可能拿到未初始化完成的对象(JVM 会把对象分配内存 → 调构造函数 → 赋值给静态变量 这三步重排)
- 把 synchronized 加在方法上,或只锁住整个 if 块但没做二次检查
- 构造函数没设为 private,破坏单例语义
DCL 实现必须带 volatile 的完整写法
这是目前 JDK 6+ 最稳妥的懒汉式单例写法,兼顾线程安全与性能:
public class Singleton {
private static volatile Singleton instance;
<pre class="brush:php;toolbar:false;">private Singleton() {} // 防止反射攻击可加 throw new RuntimeException()
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}}
关键点:
- volatile 禁止重排序 + 保证可见性
- 第一次判空(避免不必要的同步)
- 进入同步块后再判空(防止多线程同时通过第一层判断后重复创建)
- synchronized 锁的是类对象,不是 this,确保全局唯一锁
为什么不能用 static 内部类替代 DCL
可以,而且更简洁、无锁、天然线程安全,但面试问 DCL 就是在考你对内存模型和锁优化的理解,不是比谁写得短。
立即学习“Java免费学习笔记(深入)”;
static 内部类方式是合法且推荐的生产写法,但它不涉及 volatile 和双重检查逻辑,答不出 DCL 的底层原因(如重排序、happens-before)容易被追问卡住。
使用场景差异:
- DCL:需要延迟初始化 + 明确控制同步粒度 + 面试考察 JVM 底层
- 静态内部类:更推荐用于大多数业务代码,加载时机由类加载器保证,无需额外同步
面试最容易被揪住的三个细节
这几个点一错,基本就暴露没真写过/调过并发代码:
-
volatile缺失 —— 不是“好像能跑”,是“一定有概率出 bug”,尤其在低版本 JDK 或特定 CPU 架构下 - 把
synchronized锁在this或实例上 —— 单例还没创建,哪来的 this?编译都过不去 - 没处理反射、反序列化、克隆攻击 —— 至少提一句“生产中需配合
readResolve()和私有构造函数抛异常”
复杂点不在写几行代码,而在理解为什么那行 volatile 不可省,以及它和 synchronized 在内存屏障层面怎么协作。漏掉这个,DCL 就只是个模板,不是解决方案。










