【单例计划模式】揭秘单例模式:从原理到实战的全方位剖析(开发者必读)

[复制链接]
发表于 2026-2-12 00:35:37 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
深入明白单例计划模式:原理、实现与最佳实践

弁言

在软件开发的过程中,我们经常会碰到必要全局唯一实例的情况。比方,设置管理器、日志日志纪录器或是数据库毗连池等,这些组件通常在整个应用步调中只必要一个实例,而且可以或许被全部组件共享访问。这时,单例计划模式就显得尤为告急。它不但可以或许确保体系中某个类只有一个实例存在,还能提供一个全局访问点,从而简化了体系的计划。
为什么告急?
单例计划模式的告急性体现在以下几个方面:

  • 资源优化:通过单例模式,我们可以确保体系中只有一个实例存在,这对于那些初始化代价较高且频仍使用的对象非常有用,比如数据库毗连或文件体系操纵。
  • 全局访问:单例模式提供了一个全局的访问点,使得在任何地方都能轻松获取到同一个实例,这在必要频仍访问同一对象的情况下非常方便。
  • 易于维护:由于整个体系中只有一个实例存在,因此当必要修改该实例的举动时,只必要在一个地方举行修改即可。
为什么必要相识?
对于软件开发者来说,相识并准确使用单例模式优劣常告急的:

  • 镌汰冗余:单例模式可以制止重复创建不须要的对象实例,从而镌汰了内存斲丧。
  • 进步服从:单例模式有助于进步步调的性能,特殊是在那些创建本钱较高的对象上。
  • 易于控制:由于单例对象在整个体系中的唯一性,我们可以更轻易地控制其生命周期以及举动。
  • 代码可读性和可维护性:公道使用单例模式可以使代码更加清晰和易于明白。
然而,单例模式也有其范围性和潜伏的标题,比如增长了体系的复杂度、大概引起多线程标题等。因此,准确明白和使用单例模式对于软件开发者而言至关告急。
第一部分:计划模式简介

可查这篇文章:【计划模式入门】计划模式全剖析:23种经典模式先容与评级指南(计划师必备)
内里具体讲了23计划模式的简介,可以快速相识计划模式
模式布局
单例模式包罗如下脚色:


  • Singleton:单例

    时序图

第二部分:单例模式界说

界说
单例模式是一种常用的软件计划模式,它包管一个类只有一个实例,并提供一个全局访问点来访问该实例。这意味着无论何时何地,只要必要该类的对象,都能得到同一个实例。这种模式通常用于那些必要频仍实例化的类,但又盼望保持单一实例的情况下。
单例模式的焦点头脑和目的
焦点头脑:


  • 包管唯一性:确保类的实例是唯一的。
  • 全局访问:提供一个全局访问点来获取该实例。
目的:


  • 资源节流:制止频仍创建和烧毁对象所引起的性能开销。
  • 同一管理:方便地管理共享资源大概全局设置等。
  • 简化访问:通过一个全局访问点简化对对象的访问,不必要每次调用时都通报雷同的参数。
单例模式实用的场景

  • 体系设置:对于体系的设置信息,通常只必要加载一次,并在整个应用中保持稳固,使用单例模式可以有用地管理这些设置信息。
  • 日志日志纪录:日志日志对象在整个应用步调中大概被多个模块使用,单例模式可以确保全部的日志操纵都通过同一个日志对象来举行。
  • 线程池管理:创建线程是一个相对耗时的操纵,使用单例模式可以有用地管理线程资源,制止重复创建线程。
  • 数据库毗连:数据库毗连是昂贵的资源,单例模式可以确保应用步调中只存在一个数据库毗连实例,从而镌汰资源斲丧。
  • 缓存管理:缓存对象可以用来存储常用数据,以进步体系的相应速率,单例模式可以确保缓存对象的唯一性和同等性。
第三部分:单例模式的长处和缺点

长处

  • 包管体系内存中该类只存在一个实例:


  • 通过限定一个类只能有一个实例,单例模式可以或许确保体系中不会出现多个雷同的对象实例,从而节流内存空间。
  • 这对于那些创建本钱高的对象尤其有用,比方数据库毗连或大型设置对象。

  • 允许对唯一实例举行受控访问:


  • 单例模式提供了一个全局访问点,使得对单例对象的访问变得简单且可控。
  • 这意味着可以通过单例类的方法来控制对象的举动,比方设置、状态更改等。

  • 可以节流体系资源:


  • 由于单例模式确保了类的实例是唯一的,以是可以制止重复创建和烧毁对象带来的资源浪费。
  • 这对于必要频仍访问的资源特殊有用,如日志纪录器或缓存管理器。
缺点

  • 增长了体系的复杂度:


  • 单例模式的实现通常必要特殊的构造函数和静态方法,这大概会使类的界说变得复杂。
  • 在多线程情况中,还必要思量线程安全的标题,这会增长额外的复杂性。

  • 倒霉于单元测试:


  • 单例模式使得在单元测试中模仿单例对象变得困难。
  • 由于单例对象在整个体系中都是唯一的,以是在测试时很难隔离它们的举动,这大概导致测试变得复杂且难以维护。

  • 大概导致步调变得难以维护:


  • 当单例对象被广泛使用时,如果必要修改单例对象的举动,那么这种修改大概会影响到整个体系。
  • 这种全局性的影响使得单例对象成为体系中的一个耦合点,从而低落了体系的可维护性。
小结
固然单例模式具有显着的长处,但在现实使用中也必要审慎思量其潜伏的缺点。开发者应该权衡这些优缺点,并根据项目的具体需求来决定是否接纳单例模式。在某些情况下,更换的计划模式,如依赖注入,大概更恰当。
第四部分:单例模式的实现方式

单例模式可以通过多种方式实现,每种方式都有其特点和实用场景。下面我们将分别先容几种常见的实现方式。
懒汉式

懒汉式单例模式是在初次使用时才创建实例的模式。这种模式可以分为非线程安全和线程安全两种实现。
非线程安全的实现

这种实现方式简单易懂,但不是线程安全的,也就是说,在多线程情况下大概会出现标题。
  1. public class LazySingleton {
  2.     private static LazySingleton instance;
  3.     private LazySingleton() {}
  4.     public static LazySingleton getInstance() {
  5.         if (instance == null) {
  6.             instance = new LazySingleton();
  7.         }
  8.         return instance;
  9.     }
  10. }
复制代码
线程安全的实现(双重查抄锁定)

双重查抄锁定(Double-Checked Locking, DCL)是一种优化的线程安全实现方式,它可以制止不须要的同步开销。
  1. public class LazySingletonDCL {
  2.     private volatile static LazySingletonDCL instance;
  3.     private LazySingletonDCL() {}
  4.     public static LazySingletonDCL getInstance() {
  5.         if (instance == null) {
  6.             synchronized (LazySingletonDCL.class) {
  7.                 if (instance == null) {
  8.                     instance = new LazySingletonDCL();
  9.                 }
  10.             }
  11.         }
  12.         return instance;
  13.     }
  14. }
复制代码
饿汉式

饿汉式单例模式是在类加载时就创建实例的模式。这种方式包管了线程安全性,但大概会造成资源浪费。
静态内部类方式

使用 Java 的静态内部类特性来实现饿汉式的单例模式,这种方式既实现了线程安全,又制止了在类加载时就创建实例。
  1. public class StaticInnerClassSingleton {
  2.     private StaticInnerClassSingleton() {}
  3.     private static class SingletonHolder {
  4.         private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
  5.     }
  6.     public static StaticInnerClassSingleton getInstance() {
  7.         return SingletonHolder.INSTANCE;
  8.     }
  9. }
复制代码
摆列方式

摆列范例本身就是线程安全的,因此可以很自然地用来实现单例模式。
  1. public enum EnumSingleton {
  2.     INSTANCE;
  3.     // 实例变量和方法...
  4. }
复制代码
静态工厂方法

静态工厂方法是另一种实现单例模式的方式,它使用静态方法来创建和返回实例。
  1. public class StaticFactorySingleton {
  2.     private static StaticFactorySingleton instance;
  3.     private StaticFactorySingleton() {}
  4.     public static StaticFactorySingleton getInstance() {
  5.         if (instance == null) {
  6.             instance = new StaticFactorySingleton();
  7.         }
  8.         return instance;
  9.     }
  10. }
复制代码
摆列

摆列范例的单例模式实现轻巧且线程安全。
  1. public enum SingletonEnum {
  2.     INSTANCE;
  3.     // 实例变量和方法...
  4. }
复制代码
小结
以上先容了几种常见的单例模式实现方式,每种方式都有其特点和实用场景。在选择符合的实现方式时,必要思量到线程安全性、性能影响以及可维护性等因素。比方,双重查抄锁定恰当于必要高性能且包管线程安全的场所;而摆列方式则提供了最轻巧的线程安全实现。
第五部分:线程安全性

表明多线程情况中的标题

在多线程情况中,单例模式的实现大概会碰到线程安全标题。重要的标题在于,如果没有得当的同步步伐,多个线程大概同时实验创建单例对象的实例,从而违反了单例模式的根本原则——包管一个类只有一个实例。
分析差异实现方式下的线程安全标题

懒汉式



  • 非线程安全的实现:

    • 当多个线程同时实行 if (instance == null) 判定时,大概会导致多个线程同时进入条件块并创建新的实例,粉碎了单例性子。
    • 比方,两个线程 A 和 B 同时发现 instance 为空,并各自创建了一个新的实例。

  1. public class NonThreadSafeLazySingleton {
  2.     private static NonThreadSafeLazySingleton instance;
  3.     private NonThreadSafeLazySingleton() {}
  4.     public static NonThreadSafeLazySingleton getInstance() {
  5.         if (instance == null) {
  6.             instance = new NonThreadSafeLazySingleton();
  7.         }
  8.         return instance;
  9.     }
  10. }
复制代码


  • 线程安全的实现(双重查抄锁定):

    • 在双重查抄锁定中,第一次查抄 instance 是否为空,如果为空才举行同步。
    • 如允许以制止在 instance 已经创建后还举行同步,进步了性能。
    • 但是,如果没有准确地使用 volatile 关键字,大概会导致线程安全标题。
    • 如果 new 操纵没有完成绩返回了引用,其他线程大概会看到未完全构造的实例。

  1. public class ThreadSafeLazySingleton {
  2.     private volatile static ThreadSafeLazySingleton instance;
  3.     private ThreadSafeLazySingleton() {}
  4.     public static ThreadSafeLazySingleton getInstance() {
  5.         if (instance == null) {
  6.             synchronized (ThreadSafeLazySingleton.class) {
  7.                 if (instance == null) {
  8.                     instance = new ThreadSafeLazySingleton();
  9.                 }
  10.             }
  11.         }
  12.         return instance;
  13.     }
  14. }
复制代码
饿汉式



  • 静态内部类方式:

    • 使用 Java 的类加载机制包管了线程安全性。
    • 当外部类第一次被加载时,静态内部类不会被加载,只有在第一次调用 getInstance() 方法时才会加载内部类并创建实例。
    • 由于类加载机制本身是线程安全的,因此这种方式也是线程安全的。

  1. public class StaticInnerClassSingleton {
  2.     private StaticInnerClassSingleton() {}
  3.     private static class SingletonHolder {
  4.         private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
  5.     }
  6.     public static StaticInnerClassSingleton getInstance() {
  7.         return SingletonHolder.INSTANCE;
  8.     }
  9. }
复制代码


  • 摆列方式:

    • 摆列范例的单例实现天生就是线程安全的。
    • 摆列的构造函数会在 JVM 加载时由 JVM 主动包管线程安全地初始化。
    • 因此,这种方式不必要额外的同步步伐。

  1. public enum SingletonEnum {
  2.     INSTANCE;
  3.     // 实例变量和方法...
  4. }
复制代码
提供办理方案和发起



  • 使用 volatile 关键字:

    • 在懒汉式实现中使用 volatile 关键字来确保 instance 变量的可见性和克制指令重排序,从而制止了线程安全标题。

  1. private volatile static ThreadSafeLazySingleton instance;
复制代码


  • 双重查抄锁定:

    • 实用于必要在运行时创建实例的懒汉式实现。
    • 确保在多线程情况下也能准确地创建单例对象。

  • 静态内部类方式:

    • 保举用于必要在类加载时创建实例的饿汉式实现。
    • 这种方式不但线程安全,而且制止了在类加载时就创建实例的性能丧失。

  • 摆列方式:

    • 最简单的线程安全实现。
    • 实用于不必要额外功能的简单单例实现。

小结
在多线程情况中,单例模式的实现必要特殊注意线程安全标题。差异的实现方式有差异的优缺点,选择符合的方式取决于具体的应用场景和性能需求。使用摆列或静态内部类通常是较为保举的做法,由于它们既简单又高效。
第六部分:单例模式在现实项目中的应用

单例模式在现实项目中有广泛的应用,下面摆列了一些典范的使用场景,并分析了为什么在这些场景中选择使用单例模式。
应用案例 1: 日志纪录器

案例形貌:
在大多数应用步调中,日志纪录是一项根本的需求。通常,我们必要纪录错误、告诫以及调试信息等。为了方便管理和同一设置,日志纪录器通常被计划为单例。
为什么选择单例模式:


  • 同一管理:通过单例模式,可以确保应用步调中只有一个日志纪录器实例,如允许以会合控制日志级别、输特殊式等设置。
  • 简化访问:单例模式提供的全局访问点使得在步调的任何位置都可以轻松地纪录日志信息。
  • 资源节流:由于日志纪录器通常必要频仍使用,单例模式可以制止多次创建和烧毁实例所带来的资源浪费。
示例代码:
  1. public class LoggerSingleton {
  2.     private static LoggerSingleton instance;
  3.     private Logger logger;
  4.     private LoggerSingleton() {
  5.         logger = Logger.getLogger("MyLogger");
  6.     }
  7.     public static LoggerSingleton getInstance() {
  8.         if (instance == null) {
  9.             synchronized (LoggerSingleton.class) {
  10.                 if (instance == null) {
  11.                     instance = new LoggerSingleton();
  12.                 }
  13.             }
  14.         }
  15.         return instance;
  16.     }
  17.     public void log(String message) {
  18.         logger.info(message);
  19.     }
  20. }
复制代码
应用案例 2: 数据库毗连池

案例形貌:
数据库毗连是昂贵的资源。为了进步性能,通常使用数据库毗连池来管理毗连,如允许以复用现有的毗连而不是每次都必要新建一个。
为什么选择单例模式:


  • 资源管理:毗连池必要在整个应用步调中被共享,单例模式可以确保毗连池被准确地管理。
  • 服从提拔:通过单例模式,可以制止每次查询时都创建新的数据库毗连,从而进步了应用步调的团体性能。
  • 同一设置:单例模式使得毗连池的设置可以在一个地方举行,便于维护和调解。
示例代码:
  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.SQLException;
  4. import java.util.concurrent.ConcurrentLinkedQueue;
  5. public class DBConnectionPoolSingleton {
  6.     private static DBConnectionPoolSingleton instance;
  7.     private ConcurrentLinkedQueue<Connection> availableConnections;
  8.     private DBConnectionPoolSingleton(int poolSize) {
  9.         availableConnections = new ConcurrentLinkedQueue<>();
  10.         for (int i = 0; i < poolSize; i++) {
  11.             try {
  12.                 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
  13.                 availableConnections.add(conn);
  14.             } catch (SQLException e) {
  15.                 e.printStackTrace();
  16.             }
  17.         }
  18.     }
  19.     public static DBConnectionPoolSingleton getInstance(int poolSize) {
  20.         if (instance == null) {
  21.             synchronized (DBConnectionPoolSingleton.class) {
  22.                 if (instance == null) {
  23.                     instance = new DBConnectionPoolSingleton(poolSize);
  24.                 }
  25.             }
  26.         }
  27.         return instance;
  28.     }
  29.     public Connection getConnection() {
  30.         return availableConnections.poll();
  31.     }
  32.     public void releaseConnection(Connection conn) {
  33.         if (conn != null) {
  34.             availableConnections.add(conn);
  35.         }
  36.     }
  37. }
复制代码
应用案例 3: 设置管理器

案例形貌:
应用步调通常必要读取设置文件中的信息,比方服务器地点、端标语、用户名暗码等。为了方便管理这些设置信息,通常会计划一个设置管理器。
为什么选择单例模式:


  • 全局访问:设置管理器必要在整个应用步调中被访问,单例模式可以确保设置信息的全局可用性。
  • 镌汰开销:设置信息通常在应用步调启动时加载一次,之后不再变革。单例模式可以制止重复加载设置文件,从而镌汰了开销。
  • 简化维护:全部的设置信息都在单例对象中维护,便于维护和更新。
示例代码:
  1. import java.util.Properties;
  2. public class ConfigurationManager {
  3.     private static ConfigurationManager instance;
  4.     private Properties properties;
  5.     private ConfigurationManager() {
  6.         properties = new Properties();
  7.         try {
  8.             properties.load(ConfigurationManager.class.getResourceAsStream("/config.properties"));
  9.         } catch (Exception e) {
  10.             e.printStackTrace();
  11.         }
  12.     }
  13.     public static ConfigurationManager getInstance() {
  14.         if (instance == null) {
  15.             synchronized (ConfigurationManager.class) {
  16.                 if (instance == null) {
  17.                     instance = new ConfigurationManager();
  18.                 }
  19.             }
  20.         }
  21.         return instance;
  22.     }
  23.     public String getProperty(String key) {
  24.         return properties.getProperty(key);
  25.     }
  26. }
复制代码
小结
通过这些案例,我们可以看到单例模式在现实项目中的应用代价。它不但可以或许简化代码布局,还可以进步资源的使用率,同时包管了体系的稳固性和可维护性。在选择使用单例模式时,开发者必要思量实在用性和潜伏的标题,并接纳相应的步伐来制止这些标题的发生。
固然可以。下面是关于单例模式最佳实践的具体内容:
第七部分:单例模式的最佳实践

单例模式固然强大,但也轻易被误用。下面是一些关于怎样公道使用单例模式的最佳实践:
怎样制止滥用单例模式


  • 明白使用场景:


  • 确定单例模式是否真的恰当当前的场景。思量是否真的必要一个全局唯一的实例,以及这个实例是否会被频仍地访问。
  • 如果可以使用局部变量或依赖注入等其他方式来代替,应优先思量这些更换方案。

  • 制止过分依赖:


  • 只管镌汰代码对单例对象的直接依赖。过分依赖单例对象会导致代码耦合度过高,难以测试和维护。
  • 思量使用依赖注入框架来管理对象的生命周期,这可以进步代码的机动性和可测试性。

  • 思量更换方案:


  • 对于那些不必要全局唯一实例的场景,可以思量使用工厂模式或依赖注入等其他计划模式来更换单例模式。
如那边置处罚单例模式与其他计划模式的团结


  • 与依赖注入团结:


  • 纵然使用单例模式,也可以通过依赖注入框架(如 Spring、Guice 等)来管理单例对象的生命周期。
  • 如允许以在包管单例特性的根本上,进步代码的可测试性息争耦。

  • 与工厂模式团结:


  • 如果单例对象必要根据差异的情况举行创建,可以思量使用工厂模式来创建单例对象。
  • 比方,可以根据差异的设置文件或运行情况创建差异的单例实例。
保举的使用场景和注意事项


  • 保举的使用场景:


  • 全局设置:比方,设置管理器、日志纪录器等必要在整个应用中共享且只需一个实例的组件。
  • 资源管理:比方,数据库毗连池、线程池等资源麋集型对象。
  • 状态跟踪:比方,全局的状态管理器,用于跟踪整个应用的状态信息。

  • 注意事项:


  • 制止全局状态:只管镌汰对全局状态的依赖,由于这大概会导致不可推测的举动。
  • 思量线程安全性:在多线程情况中使用单例模式时,必须确保实在现是线程安全的。
  • 制止紧耦合:只管镌汰代码对单例对象的直接依赖,以低落耦合度。
  • 思量可测试性:

    • 在计划单例模式时,要思量怎样使其易于单元测试。比方,可以思量使用摆列或静态内部类的方式来实现单例,这些方式通常更轻易测试。
    • 如果大概,只管使用依赖注入来管理单例对象,如允许以在测试时更轻易地更换或模仿这些对象。

小结
单例模式是一种强大的工具,但在使用时必要注意其潜伏的标题。遵照上述最佳实践可以资助开发者公道地使用单例模式,制止其潜伏的风险,同时进步代码的质量和可维护性。
固然可以。下面是关于结语部分的具体内容:
总结

在这篇文章中,我们深入探究了单例计划模式的相干知识,包罗其界说、焦点头脑、实现方式以及在现实项目中的应用。我们从以下几个方面举行了具体的讨论:

  • 计划模式简介:表明白计划模式的根本概念,以及单例模式属于创建型模式。
  • 单例模式界说:界说了单例模式,并表明白其焦点头脑和目的。
  • 单例模式的长处和缺点:分析了单例模式的上风和潜伏的缺点。
  • 单例模式的实现方式:先容了懒汉式、饿汉式、静态工厂方法等多种实现方式,并重点讨论了线程安全标题。
  • 线程安全性:分析了差异实现方式下的线程安全标题,并提供相识决方案。
  • 现实应用:展示了单例模式在现实项目中的几个典范应用案例。
  • 最佳实践:给出了怎样公道使用单例模式的发起,以及怎样制止其潜伏的标题。
对未来趋势的预测
随着软件开发范畴的不绝发展和技能的进步,计划模式也在不绝进化和发展。未来的趋势大概包罗:


  • 更高级别的抽象:随着语言特性和框架的发展,未来的单例模式实现大概会变得更加轻巧和高效。
  • 容器化和微服务架构:在容器化和微服务架构中,单例模式的使用方式大概会有所差异,必要思量服务间的通讯和资源管理。
  • 异步编程:随着异步编程的遍及,如安在异步情况中准确地使用单例模式也是一个值得研究的方向。
固然单例模式在很多情况下都非常有用,但它并不是实用于全部场景的灵丹仙丹。现实上,计划模式范畴非常广阔,尚有很多其他模式值得学习和探索,比方工厂模式、战略模式、观察者模式等等。每种模式都有其独特的应用场景和上风,把握更多的计划模式将有助于您成为一名更良好的开发者。
鼓励读者在现实工作中积极应用所学的计划模式,并在碰到标题时机动选择最恰当的模式。同时,也发起读者深入研究计划模式背后的原理,以便可以或许更好地明白和顺应不绝变革的技能情况。
末了,盼望本文可以或许资助您更好地明白和使用单例计划模式,并引发您对其他计划模式的爱好。感谢您的阅读!
本文具体先容了23种计划模式的根本知识,资助读者快速把握计划模式的焦点概念,并找到恰当现实应用的具体模式:
【计划模式入门】计划模式全剖析:23种经典模式先容与评级指南(计划师必备)
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表