网站建设现在市场大不大,网站做推广 建设哪种类型合适,高青云速网站建设,站长交易网在软件开发的赛场上#xff0c;单例模式以其独特的魅力长期占据着重要的地位。作为设计模式中的一员#xff0c;它在整个软件工程的棋盘上扮演着关键性角色。本文将带你深入探索单例模式的神秘面纱#xff0c;从历史渊源到现代应用#xff0c;从基础实现到高级技巧#xf… 在软件开发的赛场上单例模式以其独特的魅力长期占据着重要的地位。作为设计模式中的一员它在整个软件工程的棋盘上扮演着关键性角色。本文将带你深入探索单例模式的神秘面纱从历史渊源到现代应用从基础实现到高级技巧经过戏剧性的转折和层层推进我们将一步步揭开这一模式背后的秘密。 首先文章将串起时间的线索带你重回单例模式的起源理解它在软件工程历史中的地位。经过时间的流逝单例模式不仅保持了其原有的魅力而且随着新的编程语言和技术的发展还展示出了新的活力和应用场景。 接着细致解码单例模式的基础结构为你展现其底层的实现方式。通过对不同实现策略的探讨我们会评估各自的优势和潜在风险帮助你作出明智的设计选择。无论是饿汉式的立即加载还是懒汉式的延迟兴建或是静态内部类和枚举方式的现代演化本文将在详尽的案例分析中为你搭建一座走向单例模式深入理解的桥梁。 文中还将精心打造实战场景凸显单例模式在不同应用中的策略运用。从多线程的并发控制到资源访问的优化从全局状态管理到服务类的持久化单例模式如何巧妙地解决现实问题——这些都将在本文中一一揭晓。 在“单挑编程世界”的冒险中你将体验到单例模式的实践力量。面对代码的战场这篇文章将成为你的秘密武器助你披荆斩棘挖掘单例模式背后藏匿的珍宝。无论你是设计模式的新手还是寻求更高级理解的资深开发者本文都将与你并肩作战一起探索编程世界的深奥与精彩。 目录
一、初识
二、案例 2.1 分析与实现-懒汉式 2.2 分析与实现-静态内部类 2.3 分析与实现-饿汉模式 2.4 分析与实现-双重检查锁 2.5 分析与实现-枚举
三、讲解 3.1 功能 3.2 范围 3.3 调用顺序示意图 3.4 优缺点
四、练习 一、初识 单例模式的历史渊源可以追溯到上世纪90年代当时设计模式的概念由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides等人在《设计模式可复用面向对象软件的基础》一书中首次提出。 最初的设计模式书籍中并没有明确提到单例模式但许多后来的书籍和文章将其作为设计模式的一部分进行讨论和描述。单例模式的概念是为了解决在软件开发中需要创建一个全局唯一对象的问题。 随着编程语言和技术的发展单例模式逐渐成为软件工程领域的重要模式之一并广泛应用于各种类型的应用程序和系统中。它在许多领域具有实际应用包括但不限于以下几个方面 1. 资源管理单例模式常用于管理共享资源例如数据库连接池、线程池、日志管理器等。通过使用单例模式可以确保在整个应用程序中只有一个实例存在从而有效地管理资源和提高性能。 2. 配置管理单例模式在配置管理中也扮演着重要角色。例如一个应用程序可能需要读取配置文件中的信息并在整个应用程序中共享这些配置信息。通过使用单例模式可以确保在整个应用程序生命周期中只有一个配置对象存在。 3. 全局状态管理在某些情况下需要在应用程序中维持一些全局状态数据例如游戏中的玩家信息、应用程序的运行时状态等。通过使用单例模式可以方便地访问和更新这些全局状态数据并确保数据的一致性和唯一性。 4. 日志记录单例模式常用于日志记录器的实现。通过使用单例模式可以确保在整个应用程序中只有一个日志记录器实例从而统一管理和记录日志信息。 5. 缓存管理在需要缓存数据的场景中单例模式可以用来实现缓存管理器。通过单例模式可以确保整个应用程序中只有一个缓存管理器实例提高缓存的效果和一致性。 6. 节约内存和提高性能由于单例模式只创建一个实例可以减少内存占用并且在运行时能够提供更高的性能。这在需要频繁创建和销毁对象的场景中尤为重要如线程池、网络连接等。 7. 限制实例化和控制访问单例模式可以限制一个类只能创建一个实例并提供一个统一的访问点。这可以防止其他对象直接实例化该类从而增加了对实例化过程的控制和安全性。 8. 模块化和解耦合单例模式可以帮助实现模块化和解耦合在应用程序中以模块的形式组织和管理代码。单例模式将整个模块封装在一个类中对外暴露统一的接口使得模块之间的交互更清晰和简化。 二、案例 场景银行推出一个新的产品就叫新产品了通过获取个人缴纳部分的公积金额度乘以某倍数得到的总金额来作为准入的其中一个规则和计算授信额度的标准。 ... 最终就是调某个接口叫外部产品了来获取客户的公积金信息。外部产品SDK说明第一步是实例一个Client由Client负责组装数据、加密解密等然后发post请求来获取公积金信息。 2.1 分析与实现-懒汉式 考虑到是尝试新产品和部分地区尝试上线、硬件软件、服务器性能情况等综合因素考虑选择使用懒汉式来实例化产品Client。 实现示例代码
public class LazySingleton {private static SdkClient sdkClient;private LazySingleton() {// 私有构造函数}public static synchronized SdkClient getInstance() {if (sdkClient null) {sdkClient new SdkClient();}return sdkClient;}
} 在懒汉模式中构造函数是私有的确保外部无法直接实例化该类。getInstance() 方法返回单例对象的实例并在首次调用时创建对象。 优点
延迟加载最大限度地节省资源和提高性能。只有在需要时才会创建实例适用于大型对象或高开销资源的情况。线程安全通过同步处理来解决多线程环境下的竞态条件。 缺点
需要考虑线程安全问题可能需要使用同步机制如使用synchronized关键字或通过双重检查锁定等。同步机制会增加额外的开销可能会影响性能。可能存在序列化和反射攻击的漏洞需要做相应的处理来防止。 2.2 分析与实现-静态内部类 静态内部类方式利用Java的类加载机制来实现懒汉式的延迟加载。 实现示例代码
public class StaticInnerSingleton {private StaticInnerSingleton() {// 私有构造函数}private static class SingletonHolder {private static final SdkClient instance new SdkClient();}public static SdkClient getInstance() {return SingletonHolder.instance;}
} 在这个示例中私有的构造函数确保了外部无法直接实例化该类的对象。通过静态内部类SingletonHolder来持有单例对象并且该类的实例化操作是在静态初始化阶段即类加载时完成的保证了线程安全性。 优点 懒加载单例对象的实例化在调用getInstance()方法时才会执行实现了延迟加载的效果。 线程安全由于静态内部类的特性当多个线程同时访问getInstance()方法时静态内部类的实例化操作只会执行一次保证了线程安全性。 简洁性相对于双重检查锁等方式使用静态内部类实现单例模式的代码更加简洁明了。 缺点 需要了解并理解静态内部类的工作原理虽然使用静态内部类实现单例模式代码相对简洁但是理解这种实现方式的工作原理可能需要一定的知识和经验。对于不熟悉静态内部类的开发者来说可能需要花费一些时间来理解其背后的概念和实现细节。 不支持传递参数的实例化静态内部类实现的单例模式无法直接传递参数给单例对象的构造函数因为静态内部类的实例化是在类加载时完成的。如果需要传递参数的实例化可能需要使用其他方式实现。 需要额外的类使用静态内部类实现单例模式需要额外定义一个静态内部类来持有单例对象这增加了代码中的类数量。虽然这对于代码的组织和结构有一定的好处但对于一些简单的应用场景来说可能显得有些冗余。 序列化和反序列化的处理如果单例对象需要支持序列化和反序列化操作需要额外处理否则在反序列化时会得到不同的实例。可以通过实现readResolve()方法来解决这个问题。 2.3 分析与实现-饿汉模式 因为支行的各种软件产品的建设都要报备到省总部由于历史某种原因以前报软件产品比较少。所以好多产品都集成一个应用上一个应用几乎每有新功能需要上线这种频繁上线方式肯定是不建议的但有时候又是非常必须的导致某天第一个客户经理办业务时失败查原因是实例化和发post请求超时了内网向外网发请求网络上做了好多层的代理转发。小白就想着那就修改一下外部产品Client的实例化方式吧。 痛点第一个应用加载太慢导致业务办理失败。 实现示例代码
public class EagerSingleton {private static final SdkClient sdkClient new SdkClient();private EagerSingleton() {// 私有构造函数}public static SdkClient getInstance() {return sdkClient;}
} 在饿汉模式中单例实例在类加载时就被创建并且是静态的、final的常量。getInstance() 方法直接返回该实例而无需进行额外的创建过程。 由于在类加载时就创建了单例实例因此在多线程环境中是线程安全的不需要额外处理同步问题。 优点
线程安全由于在类加载时就创建实例因此不会存在多线程并发访问创建实例的问题无需考虑同步和线程安全。简单明了代码相对较为简单不需要额外的同步处理逻辑清晰。 缺点
资源浪费在某些情况下如果单例对象的创建和初始化比较耗时而且在程序的整个生命周期中可能并不会被立即使用就会造成资源的浪费。引入不必要的依赖饿汉模式可能会在应用启动时加载大量实例增加了启动时间还可能引入不必要的依赖关系。 2.4 分析与实现-双重检查锁 有段时间发现服务器出现频繁的GC排查发现大量的实例使用比较少但是又不能没有其中就有外部产品Client对象。最近服务器和网络都做了调优再加上产品已大量推广使用在某一些高峰期可会存在大量并发的情况等等种种原因综合考虑。小白就想着那就修改一下外部产品Client的实例化方式吧。用双重检查锁来实现实例化外部产品Client对象。 实现示例代码
public class DoubleCheckedSingleton {private volatile static SdkClient sdkClient;private DoubleCheckedSingleton() {// 私有构造函数}public static SdkClient getInstance() {if (sdkClient null) {synchronized (SdkClient.class) {if (sdkClient null) {sdkClient new SdkClient();}}}return sdkClient;}
} 在双重检查锁模式中通过在getInstance()方法中使用双重检查来确保只有在实例为null时才创建新的实例。
第一次检查检查实例是否已经创建如果没有创建过则进入同步块。第二次检查在同步块中再次检查实例是否已经创建这是为了避免在多个线程同时通过第一次检查的情况下都进入同步块创建实例从而造成多次实例化的问题。 为了确保线程之间的可见性需要使用volatile关键字修饰instance变量。volatile关键字的作用是禁止指令重排保证每个线程都能正确看到instance变量的最新值避免在多线程环境下出现问题。 优点
延迟加载双重检查锁可以在需要时才创建实例避免了一开始就创建实例造成的资源浪费。提高性能只有在第一次获取实例时才需要同步后续获取实例时无需进行同步提高了性能。 缺点
使用双重检查锁实现单例模式仍然需要谨慎对待确保代码正确性代码的复杂性增加了维护和理解的难度可能会导致潜在的bug。 2.5 分析与实现-枚举 实现示例代码
public enum EnumSingleton {INSTANCE;private int value;EnumSingleton() {// 构造函数可以添加初始化逻辑value 10;}public int getValue() {return value;}
} 在这个示例中EnumSingleton是一个枚举类其中INSTANCE是唯一的枚举常量代表了单例对象。在构造函数中我们可以添加额外的初始化逻辑。在这里我们将value初始化为10。 假设在其他地方调用时可以通过EnumSingleton.INSTANCE来获取初始对象并调用其方法
int value EnumSingleton.INSTANCE.getValue();
System.out.println(value); // 输出10 通过枚举类的常量INSTANCE获取到初始对象然后可以调用其方法和访问其成员变量。 优点
线程安全枚举实现的单例模式在创建实例时是线程安全的。枚举常量的实例化在类加载时完成保证了全局只有一个实例并且在多线程环境下也是安全的。防止反序列化创建新对象枚举类默认实现了Serializable接口并且枚举对象的反序列化操作不会创建新的对象。这使得枚举单例在涉及到序列化和反序列化的场景中更加安全。简单明了使用枚举实现单例模式代码简洁明了不需要编写复杂的线程安全逻辑或者使用双重检查锁等方式只需声明一个枚举常量即可。 缺点
不能延迟加载枚举实现的单例模式在类加载时就完成了实例化因此无法实现延迟加载的需求。如果应用在初始化时对资源消耗较大无法延迟加载可能会影响应用的性能。有限的扩展性枚举常量在枚举类中是固定的无法在运行时动态地添加额外的枚举常量。这意味着枚举单例模式的扩展性相对受限无法通过添加更多的实例来应对不同的需求。 三、讲解 单例模式的本质控制实例数目。 3.1 功能 单例模式是用来保证这个类在运行期间只会被创建一个类实例另外单例模式还提供了一个全局唯一访间这个类实例的访问点就是getlnstance 方法。不管采用懒汉式还是饿汉式的实现方式这 个全局访问点是一样的。 对于单例模式而言不管采用何种实现方式它都是只关心类实例的创建问题并不关心具体的业务功能。 3.2 范围 目前Java 里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的所以 一个虚拟机在通过自己的ClassLoader 装载饿汉式实现单例 类的时候就会创建一个类的实例。 这就意味着如果一个虚拟机里面有很多个ClassLoader而且这些ClassLoader 都装载某个类的话就算这个类是单例它也会产生很多个实例。当然如果一个机器上有多个虚拟机的话那么每个虚拟机里面都应该至少有一个这个类的实例也就是说整个机器上就有很多个实例更不会是单例了。 3.3 调用顺序示意图 3.4 优缺点 1. 时间与空间
比较懒汉式和饿汉式懒汉式是典型的时间换空间也就是每次获取实例都会进行判断看是否需要创建实例浪费判断的时间。当然如果一直没有人使用的话那就不会创建实例则节约内存空间。饿汉式是典型的空间换时间当类装载的时候就会创建类实例不管你用不用先创建出来然后每次调用的时候就不需要再判断 了节省了运行时间。 2. 线程安全
从线程安全性上讲不加同步的懒汉式是线程不安全的。饿汉式是线程安全的因为虚拟机保证只会装载一次在装载类的时候是不会 发生并发的。如何实现懒汉式的线程安全懒汉式也是可以实现线程安全的只要在getInstance()方法上加上synchronized 即可。加上synchronized会降低整个访问的速度而且每次都要判断。双重检查加锁可以使用“双重检查加锁” 的方式来实现就可以既实现线程安全又能够使性能不受到很大的影响并不是每次进入getlnstance 方法都需要同步而是先不同步进入方法过后先检查实例是否存在如果不存在才进入下面的同步块这是第一重检查。进 入同步块过后再次检查实例是否存在如果不存在就在同步的情况下创建一个实例这是第二重检查。这样一来就只需要同步一次了从而减少了多次在同步情况下进行判断所浪费的时间。双重检查加锁机制的实现会使用一个关键字volatile它的意思是:被volatile 修饰的变量的值将不会被本地线程缓存所有对该变量的读写都是直接操作共享内存从而确保多个线程能正确的处理该变量。 四、练习 请编写 Triple类实现最多只能生成3个 Triple类的实例实例编号分别为 0 , 1 , 2且可以通过 getInstance(int id)来获取该编号对应的实例。