Java单例模式:双检锁陷阱与最佳实践

📅 2026/8/19 11:22:30
Java单例模式:双检锁陷阱与最佳实践
老实说双重检查锁在Java界用的人多出锅的也多。干嘛非得采用双重检查呢, 是由于我们存有偷懒的想法, 没错, 确实是偷懒, 同步所产生的开销很大, 因而想着让同步仅在必要的情形下才触发, 这种必要情形便是 “实例还未曾被创建” 的时候, 然而在早年年头的 Java 版本当中这事儿事实上是存在不少坑的。先看代码——经典的双重检查写法大家面试背得都很熟public class Singleton { privatevolatilestatic Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { // 第一次检查 synchronized (Singleton.class) { if (instance null) { // 第二次检查 instance new Singleton(); } } } return instance; } }关注要点, 这个是必定要有的, 倘若没有, 那么你所拥有的这种双检锁便是无效的。怎么会这么讲, 究竟是基于何种缘由才这般表述, 这是由于在JVM创建新对象之际, 实际上是开展过诸多繁杂多样的额外操作的。难道你会认为仅仅一句 new ();就全然是单一步骤的操作行为么, 抱持这种想法可就大错特错过于幼稚天真了, 事实上它确切无疑能够被拆解划分成为恰好三步的:分配内存调用构造函数将内存地址赋给那JVM, 它是存在指令重排序这种情况的, 有可能先把引用给赋了值, 之后才去初始化对象。你说这是不是很坑? 如此一来, 某个线程所拿到的就极有可能是个“半成品”。这就好比你早上到食堂去打饭, 阿姨递给你一个尚未熟透的鸡腿, 还说,“这是熟的”你敢去吃吗?自JDK 1.5起始, 语义得以强化, 使得写入操作的“-”这种关系得到保障, 进而才致使此方法变得安全且可靠。但话又说回来, 这么个写法, 我着实不建议在线上系统当中使用。为何呢? 因为它太易于出现差错了, 特别是当你所在的团队里存在实习生的情况下……我一般怎么做呢直接上静态内部类public class Singleton { private Singleton() {} privatestaticclass Holder { privatestaticfinal Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }这方式的优点显而易见你说是不是香当然了, 并非是所有的人都对这一套青睐有加, 特别是存在一些人, 一旦瞧见内部类便会萌生出密集恐惧症, 如此情况下, 那你能够思索借助枚举去达成单例:public enum Singleton { INSTANCE; public void doSomething() { // 方法逻辑 } }在Java里边, enum天然便属于那种单例形态的, 针对反序列化以及反射攻击它皆能够阻挡住当然, 除非你运用极具邪门意味的那种招数, 简直就好似开了挂一般。可是, 针对这种方式, 我通常仅仅是在那些标示为“真·单例”的场景之内采用它, 比如像那种用于注册中心或者线程池管理器这样情况, 毕竟, 枚举这个东西灵活性不是很强, 扩展性能方面也稍有点缺乏意趣。那在什么样的时候, 才应该去使用双重检查锁呢? 只是在你能够清晰明确地知道, 这个对象的构造过程是极为昂贵的, 并且懒加载会带来明显显著的受益的场景之下。好比弄个缓存系统, 初始化时得跑到数据库load一堆配置, 在这种情形下懒加载是具备意义的。不然就别去折腾这一套——直接通过静态初始化或者内部类来处理, 如此开发效率以及安全性都会更高。现在有些人连写个线程安全单例都喜欢整成 Bean……反正, 都是能够去进行管理的, 你做一个, 再配合一个Scope(), 这样不就把事情完成了?并不是说这不行, 而是在于你把原本简单的问题给复杂化了。单例, 本来JVM自身是能够将其搞定的, 可你却非要弄出IOC来进行托管, 这情形是不是隐隐有些吃饱了却没事找事, 存在那样一种嫌疑?据此得出的结论相当简明: 技术乃是用来为业务提供服务的, 切莫只为了“显摆帅气”而去施展一些充满玄技色彩的设计, 最终导致团队承担相关责任, 而你自己却要承担主要责任, 这类事情我可是见识过许多回了……