尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
设计模式01-单例模式:唯一实例的四种实现与选型
单例模式高并发下唯一实例的四种实现与选型本文梳理 Java 中单例模式的四种经典实现各自的取舍以及高并发场景下最容易踩的两个坑。一、单例在解决什么问题单例模式Singleton回答一个朴素的问题有些对象整个系统只需要一个。比如配置中心启动时加载一次全局共享订单号生成器所有订单号由同一个发号器产出数据库连接池连接资源集中管理不能各处各建一个池子日志收集器所有模块的日志汇入同一条管道。它的约束有三条类只能有一个实例这个实例由类自己创建并向全系统提供全局访问入口。听起来简单但放进高并发环境问题立刻变得立体挑战具体问题线程安全多个线程同时创建如何保证只产生一个实例性能加锁之后每次获取实例都抢锁吞吐怎么办防破坏反射、序列化反序列化能否绕过限制再造一个实例Java 社区给出的四种实现本质上就是在懒加载、锁开销、安全性三个维度上做不同的取舍。下面逐一展开。二、饿汉式类加载即创建简单到没有并发问题最直接的思路在类加载阶段就把实例创建好。JVM 保证类的静态初始化只执行一次且线程安全clinit方法会加锁所以压根不存在竞争。/** * 饿汉式单例 * 场景系统配置中心——启动必用、全局唯一、全程不变 */publicclassConfigCenter{// 类加载时即完成实例化JVM 保证线程安全privatestaticfinalConfigCenterINSTANCEnewConfigCenter();// 私有构造器阻断外部 newprivateConfigCenter(){loadConfig();}publicstaticConfigCentergetInstance(){returnINSTANCE;}publicStringget(Stringkey){returnvalue-of-key;}privatevoidloadConfig(){System.out.println([ConfigCenter] 配置加载完成类加载时执行);}}取舍分析✅ 无锁所有线程读到的是同一个 final 引用读取没有任何同步开销高并发下性能最好✅ 简单三行核心代码几乎不可能写错❌ 没有懒加载类被加载实例就创建如果实例占用资源大比如提前建立连接池而系统运行很久才用到它就是白白占着内存❌ 无法动态初始化构造器不能带参数想根据启动参数配置实例就做不到。适用判断实例轻量、启动必用的场景——配置管理器、全局常量、工具类。这也是生产中最常见的写法。三、双重检查锁DCL懒加载与性能的平衡如果实例创建成本高建立连接、初始化算法参数又不想启动时就付出这个成本就需要懒加载第一次用到才创建。最朴素的做法是给getInstance整个方法加synchronized但那样每次获取实例都要抢锁——实例创建好之后抢锁纯属浪费。于是有了双重检查锁Double-Checked Locking只在实例为空时才进同步块创建完成后全部无锁。/** * 双重检查锁DCL单例 * 场景订单号生成器——首次生成订单时才初始化雪花算法参数 */publicclassOrderNoGenerator{// volatile 关键禁止指令重排下文详解privatestaticvolatileOrderNoGeneratorINSTANCE;privateOrderNoGenerator(){initSnowflake();}publicstaticOrderNoGeneratorgetInstance(){// 第一重检查绝大多数调用在这里直接返回不碰锁if(INSTANCEnull){// 只有创建阶段才加锁synchronized(OrderNoGenerator.class){// 第二重检查防止多个线程排队等锁后重复创建if(INSTANCEnull){INSTANCEnewOrderNoGenerator();}}}returnINSTANCE;}// 高频调用路径上没有任何锁publiclongnextId(){returnSystem.currentTimeMillis()100_000L;}privatevoidinitSnowflake(){System.out.println([OrderNoGenerator] 雪花算法参数初始化首次 getInstance 时执行);}}为什么 volatile 不能省new OrderNoGenerator()在 JVM 层面不是一步完成的它至少拆成三步分配对象内存执行构造器初始化把内存地址写入 INSTANCE 引用。CPU 和 JIT 可能把第 2、3 步重排——先写引用、再初始化。于是可能出现线程 A 刚执行完第 1、3 步还没来得及初始化线程 B 经过第一重检查发现 INSTANCE 非空直接拿去用拿到的是一个半初始化的对象后续调用随时可能出错。volatile禁止了这种重排保证引用可见一定发生在初始化完成之后。这也是 DCL 写法里最容易漏掉的一个字——漏掉之后低并发下测不出来上线后偶发诡异 bug。取舍分析✅ 懒加载首次使用才初始化✅ 高性能创建完成后的调用完全无锁❌ 代码复杂双重检查 volatile少一个都不行容易被简化出问题。适用判断实例创建成本高、启动不一定用到的场景——ID 生成器、连接池、重量级客户端。四、静态内部类借 JVM 之手实现懒加载还不用自己加锁有没有一种写法既有饿汉式的零锁开销又有 DCL 的懒加载还不需要手动写检查逻辑有。思路是把实例的创建放进一个静态内部类而 JVM 只在内部类第一次被访问时才加载它。/** * 静态内部类单例 * 场景本地缓存管理器——懒加载 无锁 */publicclassCacheManager{privateCacheManager(){initCache();}// 外部类加载时Holder 并不会被加载privatestaticclassHolder{// 真正触发加载的是首次访问 Holder.INSTANCE 的那一刻privatestaticfinalCacheManagerINSTANCEnewCacheManager();}publicstaticCacheManagergetInstance(){returnHolder.INSTANCE;}publicObjectget(Stringkey){returnnewObject();// 示意}privatevoidinitCache(){System.out.println([CacheManager] 缓存初始化首次 getInstance 时执行);}}取舍分析✅ 懒加载 无锁加载时机由 JVM 类加载机制保证性能与饿汉式一致✅ 优雅没有 DCL 的双重检查和 volatile可读性好❌ 不能传参和饿汉式一样构造器无法接收运行期参数。适用判断需要懒加载、但不需要动态参数的场景——缓存管理器、分布式锁客户端、注册中心客户端。五、枚举JVM 层面的绝对唯一前面三种实现都挡不住两记冷箭反射Constructor.setAccessible(true)强行调用私有构造器序列化把实例序列化到磁盘再反序列化回来得到一个新对象。而枚举的实例由 JVM 直接创建反射调用枚举构造器会抛异常反序列化也走 JVM 内部机制、不会新建实例。换句话说防破坏这件事由 JVM 兜底而不是靠代码自觉。/** * 枚举单例 * 场景全局事件注册表——对唯一性要求极高 */publicenumGlobalEventRegistry{INSTANCE;GlobalEventRegistry(){initRegistry();}publicvoidpublish(Objectevent){System.out.println([GlobalEventRegistry] 发布事件: event);}publicstaticGlobalEventRegistrygetInstance(){returnINSTANCE;}privatevoidinitRegistry(){System.out.println([GlobalEventRegistry] 注册表初始化枚举类加载时执行);}}取舍分析✅ 绝对安全线程安全由 JVM 保证反射、序列化两条破坏路径都被封死✅ 极简几行代码❌ 无懒加载枚举类加载时实例即创建。适用判断唯一性比启动成本更重要的场景——全局计数器、日志总线、事件注册表。六、四种实现怎么选实现懒加载获取实例的锁开销防反射/序列化可否传参初始化一句话定位饿汉式❌无❌❌简单场景的默认选择DCL✅创建后无❌✅重实例 要懒加载静态内部类✅无❌❌懒加载的优雅写法枚举❌无✅❌唯一性至上的终极方案选型口诀实例轻、启动必用 → 饿汉式实例重、要懒加载、可能要传参 → DCL要懒加载但不用传参、想简单 → 静态内部类唯一性不容有失 → 枚举。七、两个必须记住的坑DCL 少了 volatile表面上看代码没问题低并发测试也全绿高并发下会偶发拿到未初始化完成的对象。这是单例话题里最高频的考点也是线上事故的真实来源把性能当作唯一指标很多团队不管三七二十一上 DCL其实配置中心这类轻量实例用饿汉式更合适——选型看的是资源占用 启动依赖 唯一性要求的组合而不是谁的实现更高级。小结单例的演进线其实是三问要不要懒加载能不能接受锁开销要不要防破坏四种实现分别回答这三问的不同组合。没有最好的单例只有最贴合场景的单例。
RELATED

相关推荐

数据库基础(之前的笔记)

数据库基础(之前的笔记)

数据库特点独立性(物理,逻辑(前端和后端独立开,互不影响))共享冗余度(重复去除)集中控制完整性(正确性、有效性、一致性、相容性)可恢复数据库概念模拟&抽…

📅 2026/9/11 12:30:47
操作系统笔记-2.4.4 死锁的处理策略—检测和解除

操作系统笔记-2.4.4 死锁的处理策略—检测和解除

王道操作系统笔记,视频链接:2.4.4 死锁的处理策略—检测和解除 知识总览 死锁的处理: 不允许死锁发生: 静态策略:预防死锁动态策略:避免死锁 允许死锁发生: 死锁的检测和解除(本节…

📅 2026/9/17 14:04:40
AI基本概念

AI基本概念

AI基本概念智能体 AI是Artificial Intelligence的缩写,即“人工智能”。简单说,就是让机器能像人一样‌感知、理解、学习、推理和决策‌。 /--------------------------------------------------------------------------------------------------------…

📅 2026/10/31 6:05:10
MORE NEWS

更多资讯

📰

SVG波浪动画原理与现代框架集成实战

简介:这是一份面向前端开发者与网页设计学习者的HTML5动态背景特效资源,聚焦SVG矢量动画与jQuery交互实践,适用于官网首页、活动页或作品集网站的视觉增强场景。资源以轻量级波浪翻滚动效为核心,通过SVG路径CSS3关键帧动画实现平滑…

📰

Git核心概念图解与开发实战指南

1. 为什么需要一张图讲明白Git? 作为开发者,我们每天都要和Git打交道。但每次新人入职时,看着他们面对 git rebase 时茫然的眼神,我就想起自己当年被Git支配的恐惧。Git的学习曲线就像过山车——初期陡峭得让人望而生畏&#xf…

📰

VoiceStudio本地语音合成实战:从配置调优到批量生成全攻略

最近拿到一个叫 VoiceStudio 的本地语音合成项目,我用它做了三件事:把两万字的长文转成有声版、给几段短视频配了旁白、顺手生成了几条播客片花。跑完一遍之后最大的体会是,这类工具真正的难点从来不是“能不能合成”,而是“怎么合…

📰

阶梯型矩阵与行最简形:从判定、手算到高斯消元代码

1. 阶梯型矩阵到底长什么样:先把“楼梯”这两个字拆开阶梯型矩阵这个概念,我第一次真正用上是被一个秩算错的 bug 逼出来的。当时手头有一批约束条件需要判断是否线性相关,我图省事直接调了现成库函数,结果输出跟手推的对不上&…

📰

八种笔记方法构建知识整理工作流:从输入、加工到输出

1. 先想清楚笔记到底在解决什么问题知识整理这件事,很多人一开始就走偏了:把笔记当成“抄写”,把工具当成“答案”。我曾经也是这样,看到一篇好文章先收藏,听完一场分享立刻打开软件新建文档,结果半年后翻回…

📰

解析冒充警察网络钓鱼骗局的技术手段与防范

1. 冒充警察发"传唤令"骗局的技术解析最近以色列出现了一种新型网络钓鱼骗局,犯罪分子冒充警察发送所谓的"传唤令",这种骗局已经引起全球网络安全专家的高度警惕。作为网络安全从业者,我认为有必要深入剖析这种骗局的技术…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬