当前位置: 首页 > news >正文

Java 23 种设计模式:从踩坑到精通 | Singleton —— 你写的单例真的安全吗?

Java 23 种设计模式:从踩坑到精通 | Singleton 模式 —— 你写的单例真的安全吗?

摘要:单例模式是创建型模式中最基础却也最容易埋坑的模式。本文从概念出发,拆解单例的12 种实现方式—— 从最简陋的懒汉式到最坚固的枚举,从经典的 DCL 到鲜为人知的 ThreadLocal 与 CAS 实现。同时深入剖析反射、序列化、克隆攻击的破坏手法,并给出防御方案。结合 Spring、JDK 等框架中的单例应用,让你不仅会写,更懂得在何种场景下选择何种实现。读完本文,你将拥有一份可以应对面试与生产环境的单例“兵器谱”。

📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 |当前:Singleton 单例模式| 下一篇:工厂模式
🔗 返回系列总目录


1. 从“只有一个实例”说起

为什么我们需要单例?像数据库连接池、配置管理、线程池这类系统资源,应当全局只存在一份;多个实例不仅浪费内存,还会引发状态不一致。单例模式通过私有构造器和静态访问方法,严格控制实例数量。

但“保证只有一个实例”远没有听上去那么简单。接下来,我们将从最基础的实现开始,逐步升级到坚不可摧的版本。

2. 模式定义与 UML

单例模式保证一个类在 JVM 中只有一个实例,并提供一个全局访问点。

  • 构造器私有 → 禁止外部 new
  • 静态变量持有唯一实例
  • 静态getInstance()作为全局访问口

📌 后续所有实现变体都围绕这一结构进行安全性和性能的增强。

3. 单例模式的 12 种写法

3.1 饿汉式 —— 类加载时直接创建

① 静态常量

publicclassSingleton{publicstaticfinalSingletonINSTANCE=newSingleton();privateSingleton(){}}

利用类初始化机制保证线程安全,但不支持延迟加载。

② 静态代码块

publicclassSingleton{privatestaticfinalSingletonINSTANCE;static{INSTANCE=newSingleton();}privateSingleton(){}}

适合需要额外初始化逻辑的场景,本质上也是饿汉式。

3.2 懒汉式 —— 用时才创建

③ 线程不安全懒汉

publicclassSingleton{privatestaticSingletoninstance;privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){instance=newSingleton();}returninstance;}}

并发下会创建多个实例,仅适用于单线程环境。

④ 同步方法懒汉

publicclassSingleton{privatestaticSingletoninstance;privateSingleton(){}publicstaticsynchronizedSingletongetInstance(){if(instance==null){instance=newSingleton();}returninstance;}}

解决了线程安全问题,但每次访问都持有锁,性能低下。

3.3 双重检查锁定(DCL) —— 性能与安全的平衡

⑤ 标准 DCL

publicclassSingleton{privatestaticvolatileSingletoninstance;privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){if(instance==null){instance=newSingleton();}}}returninstance;}}

volatile确保了对象初始化的可见性和禁止指令重排。

⚠️ 如果去掉volatile,可能拿到半初始化的对象,JIT 重排序坑了无数人。

3.4 静态内部类 —— 优雅的懒加载

⑥ 静态内部类

publicclassSingleton{privateSingleton(){}privatestaticclassHolder{privatestaticfinalSingletonINSTANCE=newSingleton();}publicstaticSingletongetInstance(){returnHolder.INSTANCE;}}

JVM 保证了类加载的线程安全,同时做到了懒加载,非常推荐。

3.5 枚举 —— 攻不破的堡垒

⑦ 枚举单例

publicenumSingleton{INSTANCE;publicvoiddoSomething(){}}
  • 反射攻击无效:Constructor.newInstance()对枚举抛出异常
  • 序列化安全:反序列化自动返回同一实例
  • 克隆安全:枚举不可克隆

3.6 容器式单例 —— 管理多种单例

⑧ 登记式(容器式)单例

publicclassSingletonManager{privatestaticMap<String,Object>map=newConcurrentHashMap<>();privateSingletonManager(){}publicstaticvoidregisterService(Stringkey,Objectinstance){map.putIfAbsent(key,instance);}publicstaticObjectgetService(Stringkey){returnmap.get(key);}}

适合系统中有多个需要管理的单例对象,Spring 的容器本质上就是这种思想的体现。

3.7 线程内单例 —— 每个线程一个实例

⑨ ThreadLocal 单例

publicclassSingleton{privatestaticfinalThreadLocal<Singleton>threadLocal=ThreadLocal.withInitial(Singleton::new);privateSingleton(){}publicstaticSingletongetInstance(){returnthreadLocal.get();}}

保证每个线程内只有一个实例,多线程间不共享。常用于数据库连接或事务上下文。

3.8 无锁实现 —— CAS 乐观锁

⑩ CAS 原子操作

publicclassSingleton{privatestaticfinalAtomicReference<Singleton>INSTANCE=newAtomicReference<>();privateSingleton(){}publicstaticSingletongetInstance(){for(;;){Singletoncurrent=INSTANCE.get();if(current!=null)returncurrent;current=newSingleton();if(INSTANCE.compareAndSet(null,current))returncurrent;}}}

通过AtomicReference实现无锁线程安全,适合高并发场景。但要注意如果构造函数很重,可能会多次执行new

3.9 防止破坏的加强版(防御式写法)

⑪ 静态内部类 + 序列化/反射防御

在静态内部类的基础上,增加:

publicclassSingletonimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateSingleton(){if(Holder.INSTANCE!=null){thrownewRuntimeException("禁止反射创建实例");}}privatestaticclassHolder{privatestaticfinalSingletonINSTANCE=newSingleton();}publicstaticSingletongetInstance(){returnHolder.INSTANCE;}// 防止反序列化破坏privateObjectreadResolve(){returnHolder.INSTANCE;}}

⑫ 枚举的变体:带属性的枚举

publicenumConfig{INSTANCE;privatePropertiesprops=newProperties();Config(){/* 加载配置 */}publicStringgetProperty(Stringkey){returnprops.getProperty(key);}}

枚举不仅可以做单例,还能封装复杂的业务逻辑。

4. 单例的破坏与防御(进阶)

除了反射和反序列化,还有克隆攻击:

publicclassSingletonimplementsCloneable{privatestaticfinalSingletonINSTANCE=newSingleton();privateSingleton(){}publicstaticSingletongetInstance(){returnINSTANCE;}@OverrideprotectedObjectclone()throwsCloneNotSupportedException{thrownewCloneNotSupportedException("单例禁止克隆");// 或者直接 return INSTANCE;}}

防御总结表

攻击方式防御手段
反射构造器内判断实例是否已存在,抛异常
序列化添加readResolve()方法返回已存在实例
克隆重写clone()方法,抛异常或返回自身

枚举天生免疫这三种攻击。

5. 12 种写法对比一览(含推荐指数)

写法线程安全懒加载防反射防序列化推荐指数
饿汉-常量★★☆☆
饿汉-静态块★★☆☆
懒汉-不安全★☆☆☆
懒汉-同步★★☆☆
DCL✗(可防)★★★★
静态内部类✗(可防)✗(可防)★★★★★
枚举★★★★★
容器式★★★☆
ThreadLocal线程内★★☆☆
CAS★★★☆
静态内部类+防御★★★★★
带属性枚举★★★★★

建议:若无需继承其他类,优先使用枚举;否则选用静态内部类并添加防御代码。

6. 常见误区与面试高频题

❌ 误区1:单例就是全局变量
单例是控制实例化,可包含业务逻辑,不是简单的static变量。

❌ 误区2:加了 synchronized 就绝对线程安全
DCL 的指令重排问题已证明光有同步块不够,还需volatile

❌ 误区3:枚举单例无法懒加载
枚举类在首次被使用时才会初始化,效果类似饿汉式,但同样是“用时加载”。

💡 面试高频追问

  • 静态内部类为什么是懒加载的?→ 内部类在首次被引用时才会被加载。
  • 枚举单例能否被反射破坏?→ 不能,Constructor.newInstance()中会对枚举进行特殊判断并抛出异常。
  • ThreadLocal 单例的典型应用场景?→ 每个线程需要独立的数据库连接或事务上下文时。
  • CAS 实现单例的缺点?→ 构造函数可能被执行多次,资源消耗大时不推荐。

7. 框架中的单例应用

  • Spring:默认 Bean 作用域为singletonDefaultSingletonBeanRegistryMap缓存所有单例 Bean,本质是容器式单例。
  • JDKjava.lang.Runtime采用饿汉式;java.lang.System中的许多工具方法也依赖单例思想。
  • Log4j/LogbackLoggerFactory.getLogger()返回的 Logger 通常是静态内部类或容器式单例。

8. 你应该选哪一种?

  • 如果单例不需要继承其他类,优先使用枚举
  • 如果需要继承或需要懒加载,使用静态内部类 + 防御代码
  • 在高并发且构造函数较轻时,CAS 实现是一个有趣的替代。
  • 若要在每个线程内保持唯一实例(如用户会话上下文),选择ThreadLocal 单例
  • 当系统中有许多单例需要统一管理时,参考容器式单例

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

  • 开篇:系列介绍与目录
  • 上一篇:系列介绍与目录
  • 当前:Singleton 单例模式(你在这里)
  • 下一篇:工厂模式三兄弟
  • 创建型模式汇总:单例、工厂、建造者、原型
  • 结构型模式汇总:适配器、装饰器、代理……
  • 行为型模式汇总:观察者、策略、模板方法……

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 24 篇文章彻底吃透设计模式,让代码设计成为你的本能。

http://www.jsqmd.com/news/887381/

相关文章:

  • 避坑指南:Sentaurus与SILVACO TCAD仿真NPN三极管,结果为啥差了几十uA?
  • 2026年5月25日博客精选
  • 2026年Q2国内主流超声治疗仪品牌排行盘点:经颅磁疗仪/膝盖超声波治疗仪/超声波治疗器/超声波治疗理疗/便携超声波治疗仪/选择指南 - 优质品牌商家
  • Dify笔记-一种知识库文件上传失败报错500解决方法
  • 拼多多核销商品
  • 三、Tucker 分解:从高阶PCA到多维数据压缩的实战解析
  • 手把手教你用C++和倍福ADS库在Ubuntu上读写PLC变量(附完整CMake配置)
  • 【DeepSeek安全测试辅助实战指南】:20年攻防专家亲授3大高危漏洞自动识别技巧
  • 从AlphaFold到药物设计:一文读懂蛋白质结构预测如何改变生物医药
  • ARM AArch32通用定时器寄存器架构与CNTHPS_TVAL详解
  • 迁移中国服务器数据到美国服务器
  • 别再自己画库了!手把手教你用立创EDA+AD19快速搞定原理图库(以BMI088为例)
  • 传统理财追求存钱越多越好,编写适度消费理财程序,计算快乐消费阀值,拒绝盲目极致存钱。
  • 卡内基梅隆大学等机构联合提出:让AI在“温故“中“知新“
  • 自制射频功率计:基于AD8317芯片,成本43欧元实现1MHz-10GHz测量
  • LM Studio使用MTP的qwen3.6-27B-以7840hs的780M为例
  • LLM推理优化:内核融合与动态批处理技术解析
  • DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程简介
  • 传统健身追求高强度运动,编写低负担轻健身规划程序,主动碎片化微运动,颠覆苦练健身观念。
  • 从零打造复古辉光管腕表:高压驱动、低功耗与微型化设计实战
  • 从Wi-Fi到蓝牙:DPSK差分相移键控在实际无线通信系统中的应用与MATLAB验证
  • 新手村任务:成为一个架构师需要哪些装备?
  • 航空发动机分布式控制系统关键技术【附代码】
  • 数组专项(二):二维数组、滑动窗口思想
  • 番茄小说下载器终极指南:三步构建你的离线阅读自由王国
  • 告别道路预测老套路:用ParkPredict+模型思路,解决停车场里的‘鬼探头’难题
  • 告别光秃秃的地形:用Unity Terrain Tools打造风格化森林与草地的进阶技巧(附素材资源推荐)
  • Python算法基础篇之分治算法原理与实战
  • 传统日程表塞满任务,编写留白日程规划程序,强制预留放空空白时段,拒绝时间被完全填满。
  • 动态目标跨镜无缝接力追踪技术在旅游景区客流疏导与异常预警场景中的应用白皮书