ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java 单例模式(Singleton Pattern)实战指南:基于 java-design-patterns 的六种实现与源码级剖析

Java 单例模式(Singleton Pattern)实战指南:基于 java-design-patterns 的六种实现与源码级剖析 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载单例模式Singleton Pattern是 Gang of Four 二十三种经典设计模式中最常用、也最容易被误用的创建型模式之一。本指南以 java-design-patterns 开源仓库中的 singleton 模块 为蓝本系统讲解单例模式的意图、六种主流 Java 实现方式饿汉式、懒加载同步式、双重检查锁、静态内部类 Holder、Bill Pugh 写法、枚举式并结合模块内源码与测试用例说明每一种实现的线程安全原理、反射防护手段与适用场景。读完本文你将能够在真实项目中根据初始化时机、并发访问和序列化等约束正确选择并落地单例实现。单例模式的意图全局唯一实例与统一访问点Ensure a Java class only has one instance, and provide a global point of access to this singleton instance.单例模式的核心目标是两条保证一个类在系统中只有一个实例并为这个唯一实例提供一个全局统一访问入口。仓库 App.java 的类注释将其表述得更精确单例模式保证每个类加载器classloader实例下类只有一个实例并提供全局访问。该模式在 Wikipedia 中的定义是单例模式是一种将类的实例化限制为单个对象的软件设计模式当系统中恰好需要一个对象来协调跨系统的动作时非常有用。真实世界类比护照签发制度一个贴近生活的类比是政府签发护照在同一个国家每位公民在同一时间只能持有一本有效护照。护照管理机构确保不会给同一个人签发重复护照公民出行时必须使用这唯一的护照它作为该公民旅行凭证的唯一、全球可识别标识。这种受控访问 唯一实例管理的机制恰好映射了单例模式在 Java 应用中如何实现高效的对象管理。用一句大白话概括确保一个特定类只会被创建出一个对象。程序示例枚举单例的极简演示仓库文档引用 Joshua Bloch《Effective Java》第 2 版 p.18 的著名论断单一元素的枚举类型是实现单例的最佳方式A single-element enum type is the best way to implement a singleton。仓库中对应的实现位于 EnumIvoryTower.java完整代码仅有public enum EnumIvoryTower { INSTANCE }使用时直接引用枚举常量即可var enumIvoryTower1 EnumIvoryTower.INSTANCE; var enumIvoryTower2 EnumIvoryTower.INSTANCE; LOGGER.info(enumIvoryTower1{}, enumIvoryTower1); LOGGER.info(enumIvoryTower2{}, enumIvoryTower2);控制台输出同一对象被两次引用哈希码一致enumIvoryTower1com.iluwatar.singleton.EnumIvoryTower1221555852 enumIvoryTower2com.iluwatar.singleton.EnumIvoryTower1221555852从源码看该枚举还重写了toString()返回getDeclaringClass().getCanonicalName() hashCode()这就是输出中类名与哈希码的来源EnumIvoryTower.java。六种 Java 单例实现方式与源码剖析仓库的 singleton/src/main/java/com/iluwatar/singleton 下提供了六种实现App.java 的类注释对每种实现的特点给出了权威说明。下面逐一展开。1. 饿汉式静态实例Eager InitializationIvoryTowerIvoryTower.java 是最基础的饿汉式实现public final class IvoryTower { private IvoryTower() { // to prevent instantiating by Reflection call if (INSTANCE ! null) { throw new IllegalStateException(Already initialized.); } } private static final IvoryTower INSTANCE new IvoryTower(); public static IvoryTower getInstance() { return INSTANCE; } }原理与要点实例在类加载阶段就通过static final字段创建JVM 保证类的初始化只发生一次因此天然线程安全无需任何同步原语私有构造器防止外部new并在构造器内检查INSTANCE ! null时抛出IllegalStateException以此抵御通过反射调用私有构造器创建第二个实例的攻击代价是实例创建时机不可控——只要类被加载即使从未调用getInstance()实例就已占用内存。如 App.java 注释所述如果你能接受放弃对实例化时机的控制这种实现就适合你。2. 懒加载 全方法同步ThreadSafeLazyLoadedIvoryTowerThreadSafeLazyLoadedIvoryTower.java 将实例化推迟到首次调用public final class ThreadSafeLazyLoadedIvoryTower { private static volatile ThreadSafeLazyLoadedIvoryTower instance; private ThreadSafeLazyLoadedIvoryTower() { if (instance ! null) { throw new IllegalStateException(Already initialized.); } } public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() { if (instance null) { instance new ThreadSafeLazyLoadedIvoryTower(); } return instance; } }要点instance声明为volatile确保多线程下的可见性getInstance()整体加synchronized保证只有一个线程能创建实例缺点非常明显App.java 注释指出整个访问方法都被同步导致访问速度很慢。因为每次读取即使实例已创建都要经过锁竞争这在热点路径上会成为性能瓶颈。3. 双重检查锁Double-Checked LockingThreadSafeDoubleCheckLockingThreadSafeDoubleCheckLocking.java 在懒加载基础上大幅缩小了锁的范围public final class ThreadSafeDoubleCheckLocking { private static volatile ThreadSafeDoubleCheckLocking instance; private ThreadSafeDoubleCheckLocking() { if (instance ! null) { throw new IllegalStateException(Already initialized.); } } public static ThreadSafeDoubleCheckLocking getInstance() { var result instance; if (result null) { synchronized (ThreadSafeDoubleCheckLocking.class) { result instance; if (result null) { result new ThreadSafeDoubleCheckLocking(); instance result; } } } return result; } }要点第一次检查无锁实例已存在时直接返回无锁开销进入同步块后第二次检查防止当前线程被阻塞期间其他线程已创建实例导致重复创建源码注释引用了 Joshua Bloch《Effective Java》第 2 版 p.283-284用局部变量result暂存实例可提升约 25% 性能因为减少了对volatile字段的直接访问次数类注释特别提醒这种写法在 Java 1.4 及更早版本中是有缺陷的Broken——旧内存模型下volatile语义不完整new的指令重排可能让其他线程读到未完全初始化的对象。只有在现代 JVMJava 5 引入新内存模型下才安全相比 ThreadSafeLazyLoadedIvoryTower它不同步整个方法只在特定条件下同步方法内部片段因此访问更快。4. 初始化期占位符Holder惯用法InitializingOnDemandHolderIdiomInitializingOnDemandHolderIdiom.java 利用 JVM 类加载机制实现既懒加载又线程安全public final class InitializingOnDemandHolderIdiom { private InitializingOnDemandHolderIdiom() { if (HelperHolder.INSTANCE ! null) { throw new IllegalStateException(Already initialized.); } } public static InitializingOnDemandHolderIdiom getInstance() { return HelperHolder.INSTANCE; } private static class HelperHolder { private static final InitializingOnDemandHolderIdiom INSTANCE new InitializingOnDemandHolderIdiom(); } }原理静态内部类HelperHolder只有在getInstance()首次被调用时才会被类加载器加载加载时才执行INSTANCE的初始化JVM 保证类初始化过程天然线程安全且只执行一次。因此不需要volatile或synchronized等特殊语言构造就能做到尽可能懒且线程安全且在所有 Java 兼容编译器与虚拟机上行为正确。仓库 App.java 注释补充说明该实现需要至少 Java 8 API 级别才能正常工作。5. Bill Pugh 实现BillPughImplementationBillPughImplementation.java 与上面的 Holder 惯用法思路一致是 Bill Pugh 提出的经典写法public final class BillPughImplementation { private BillPughImplementation() { if (InstanceHolder.instance ! null) { throw new IllegalStateException(Already initialized.); } } private static class InstanceHolder { private static BillPughImplementation instance new BillPughImplementation(); } // global access point public static BillPughImplementation getInstance() { return InstanceHolder.instance; } }要点利用 Java 内存模型关于类初始化的保证——每个类只在首次使用时初始化一次。如果类尚未被使用就不会被加载进内存也不会为静态实例分配内存。这使得单例实例懒加载且线程安全同时保持访问路径极简。注释中// global access point即模式意图中全局访问点的直接体现。6. 枚举式EnumIvoryTower前文已展示 EnumIvoryTower.java 的核心代码。枚举式单例的独特优势在于枚举常量在 JVM 层面保证唯一性天然线程安全自动处理序列化问题JVM 保证枚举序列化后反序列化仍返回同一实例无需额外实现readResolve()天然防反射攻击Java 不允许通过反射创建枚举的新实例。但 App.java 的注释也提醒其劣势绑定实现策略、无法扩展枚举类、序列化语义受限、编码自由度受限等并引用了 Stack Overflow 上的专门讨论。此外枚举中新增的普通方法若需线程安全仍由开发者自行负责。六种实现的横向对比实现方式初始化时机线程安全同步开销反射防护序列化安全IvoryTower饿汉式类加载时天然安全无构造器检查需自行处理ThreadSafeLazyLoadedIvoryTower首次调用synchronized每次访问全方法加锁最慢构造器检查需自行处理ThreadSafeDoubleCheckLocking首次调用volatile 双重检查仅首次创建时加锁较快构造器检查需自行处理InitializingOnDemandHolderIdiom首次调用类加载机制保证无构造器检查需自行处理BillPughImplementation首次调用类加载机制保证无构造器检查需自行处理EnumIvoryTower枚举类加载时JVM 保证无JVM 禁止反射创建JVM 保证测试验证如何证明单例确实只有一个实例仓库的测试代码从三个维度验证了每种实现的正确性核心测试位于 SingletonTest.java各具体实现的测试类如 ThreadSafeDoubleCheckLockingTest.java通过传入getInstance方法引用复用同一套抽象测试基类。测试 1同线程多次调用返回同一对象SingletonTest.java连续三次调用getInstance()用assertSame断言三者是同一个对象。测试 2多线程并发下仍返回同一对象SingletonTest.java创建 10000 个调用getInstance()的并发任务交给最多 8 线程的固定线程池执行在 10 秒超时约束下断言每个线程拿到的实例都与主线程的期望实例相同assertSame。这是对线程安全实现最有力的压力验证。测试 3反射攻击防护SingletonTest.java通过getDeclaredConstructor()拿到私有构造器并setAccessible(true)强行调用断言抛出InvocationTargetException——这验证了各实现构造器内的IllegalStateException(Already initialized.)检查确实挡住了反射创建第二个实例。此外 AppTest.java 验证了 App.java 的main方法可无异常执行——它依次演示了全部六种实现的双次调用与日志输出。如何运行与验证本模块是 Maven 多模块聚合项目仓库根目录 pom.xml下的一个子模块。在仓库根目录执行以下命令即可运行测试验证上述全部断言./mvnw -pl singleton test其中./mvnw是仓库自带的 Maven Wrapper对应文件 mvnw 与 mvnw.cmdWindows 下使用mvnw.cmd。想要观察各实现的双次调用输出可直接运行 App.java 的main方法例如在 IDE 中运行或通过./mvnw -pl singleton package打包后运行。何时使用单例模式根据文档在以下情况考虑使用单例模式系统中必须恰好存在一个类的实例并且客户端需要从众所周知的访问点获取它典型如全局配置、日志器、连接池、文件管理器唯一实例需要支持通过子类化扩展且客户端在不修改自身代码的前提下就能使用扩展后的实例此时通常配合工厂方法或注册表使用。真实世界中的典型应用包括日志类Logging、众多应用中的配置类Configuration、连接池Connection Pools、文件管理器File Manager以及 JDK 内置 APIjava.lang.Runtime#getRuntime()java.awt.Desktop#getDesktop()java.lang.System#getSecurityManager()优点与权衡优点Benefits对唯一实例的访问受到受控管理减少命名空间污染避免了用全局静态变量散落各处允许对操作与表示进行精化refinement若需要允许创建多个实例比类操作更灵活比纯静态类操作更灵活。权衡Trade-offs难以测试全局状态会让单元测试之间互相污染难以隔离生命周期管理更复杂单例的创建/销毁时机往往不受应用控制并发瓶颈风险如果在并发环境下使用而缺乏仔细的同步设计例如同步范围过大可能成为性能瓶颈——这正是文档中对比ThreadSafeLazyLoadedIvoryTower与ThreadSafeDoubleCheckLocking时反复强调的点。另外App.java 还专门提醒了一个分布式环境下的调试陷阱单例的 bug 在单类加载器环境下可能完全正常但在分布式/多类加载器部署中会出现每个类加载器一份实例的问题而且这类问题可能从同步现象逐渐演变为异步现象在实现很久之后才暴露排查起来相当困难。与相关设计模式的关系单例模式常与其他创建型模式协同使用抽象工厂Abstract Factory常与单例结合确保工厂类在整个应用中只有一个实例工厂方法Factory Method可用工厂方法封装单例的创建逻辑将如何创建集中管理原型Prototype原型模式避免创建新实例可与单例配合管理唯一实例的复制与使用。参考资源《Design Patterns: Elements of Reusable Object-Oriented Software》GoF 原著《Effective Java》Joshua Bloch第 2 版 p.18 关于枚举单例的论述p.283-284 关于双重检查锁性能的论述《Head First Design Patterns》《Java Design Patterns: A Hands-On Experience with Real-World Examples》《Refactoring to Patterns》仓库内完整实现singleton 模块源码 与 测试代码以及 UML 类图 PlantUML 源文件总结单例模式的本质是唯一实例 全局访问点但如何实现在 Java 中有丰富的技术层次从最简单的饿汉式、到考虑性能的双重检查锁、再到利用 JVM 类加载机制的 Holder/Bill Pugh 惯用法、以及兼顾序列化与反射攻击的枚举式。java-design-patterns 仓库用六个独立实现加三套通用测试完整演示了这些方案的取舍。实践建议是能用枚举就用枚举Joshua Bloch 的推荐需要懒加载且追求性能时优先考虑静态内部类 Holder/Bill Pugh 写法必须兼容极端老旧的 JVM 环境时再退回到全方法同步的懒加载写法同时始终警惕全局状态对可测试性和分布式部署的潜在影响。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐单例模式Singleton在 Java 中的六种实现解析以 java-design-patterns 项目源码为例单例模式Singleton在 Java 中的六种实现解析以 java design patterns 项目源码为例 单例模式Singleton是最经典示例工程教程java-design-patterns 中 Singleton 单例模式实战指南六种实现与线程安全方案全解析java design patterns 中 Singleton 单例模式实战指南六种实现与线程安全方案全解析 本文以 java design pattern示例工程教程Java 单例模式Singleton实战指南象牙塔到 Enum六种实现与源码级解析Java 单例模式Singleton实战指南象牙塔到 Enum六种实现与源码级解析 单例模式Singleton是 Java 设计模式中最基础、也最容示例工程教程上一篇Mermaid Live Editor 入门指南敲几行文字出图表零安装实时预览下一篇LeetCode-Go 题解560. Subarray Sum Equals K前缀和 哈希表 O(n) 解法全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表