姚同学 录屏 音乐 摸鱼 压图
单例模式

单例模式概述

DCL(双重检查锁定)是一种用于实现高效、线程安全单例对象的惯用法。它通过“两次检查+一次加锁”来兼顾性能与正确性。本文将结合典型代码,逐层剖析两次检查与加锁的必要性,并指出常见实现陷阱与修正方案。

第一次检查:减少同步开销

当instance已初始化完成,后续任意线程调用getInstance()时,第一次无锁判断即可直接返回结果,避免进入昂贵的同步块。该优化确保仅在首次并发访问时才会触发加锁,显著降低性能损耗。

第二次检查:保证单例语义

若去掉第二次检查,仅保留第一次检查与同步块,会出现如下异常场景:

线程T1、T2同时通过第一次检查,instance均为null;

T1获得锁并创建对象,释放锁;

T2再次获得锁,由于未做二次判断,会重新new对象,导致单例被破坏。

第二次检查在持锁期间再次确认instance状态,杜绝重复实例化,从而严格保证“全局唯一”。

加锁:确保线程安全

synchronized关键字用于排他地执行实例化逻辑,防止多线程同时进入临界区。它配合两次检查,既保证了正确性,又把锁竞争限制在最小范围。

volatile:禁止指令重排序

在没有volatile修饰的情况下,JIT编译器与处理器可能做以下重排序:

步骤1:分配内存;

步骤2:将引用地址赋给instance;

步骤3:执行构造方法完成初始化。

一旦步骤2提前于步骤3,其他线程可能拿到尚未初始化完成的对象,引发空指针或状态错误。将instance声明为volatile可插入内存屏障,禁止上述重排序,确保“对象完全构造”后才能被其他线程可见。

常见误区与修正

误区一:认为第一次检查可有可无。修正:去掉后每次调用都需进入同步块,性能急剧下降。

误区二:省略第二次检查。修正:会导致多个实例被创建,违背单例原则。

误区三:忘记添加volatile。修正:在并发场景下可能返回“半初始化”对象,产生难以重现的Bug。

结论

DCL通过“第一次检查”提升性能,“第二次检查”保障单例,“加锁”确保线程安全,再配合volatile禁止指令重排序,共同构成高效、可靠的延迟初始化方案。理解每步设计意图,才能在实际开发中正确、自信地运用DCL模式。