懒汉式单例模式

打印 上一主题 下一主题

主题 1076|帖子 1076|积分 3228

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

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

x
懒汉式单例是一种在需要时才会初始化实例的单例模式实现方式,适用于需要延迟加载的场景。以下是一个现实使用懒汉式单例的例子,并结合适用场景进行分析。

示例场景:日志管理器

在开发过程中,日志记录是一个常见需求,通常日志记录器在整个应用中只需要一个实例。使用懒汉式单例可以确保日志管理器只在第一次需要时进行初始化,从而节省体系资源。
懒汉式单例完整代码

  1. public class LogManager {
  2.     // 1. 静态变量,保存唯一实例,但不立即初始化
  3.     private static LogManager instance = null;
  4.     // 2. 私有构造方法,防止外部实例化
  5.     private LogManager() {
  6.         System.out.println("LogManager initialized!");
  7.     }
  8.     // 3. 提供一个静态方法访问唯一实例
  9.     public static synchronized LogManager getInstance() {
  10.         if (instance == null) {
  11.             instance = new LogManager();  // 延迟实例化
  12.         }
  13.         return instance;
  14.     }
  15.     // 4. 示例方法,用于记录日志
  16.     public void log(String message) {
  17.         System.out.println("Log: " + message);
  18.     }
  19. }
复制代码

代码分析


  • 静态变量 instance:

    • 静态变量 instance 用于保存 LogManager 类的唯一实例。
    • 初始值为 null,实例化操纵延后到第一次调用 getInstance() 时才进行。

  • 私有构造方法:

    • 构造方法被声明为 private,防止外部通过 new LogManager() 创建实例。
    • 在构造方法中可以放置初始化逻辑,例如配置日志文件路径等。

  • 静态方法 getInstance():

    • 是懒汉式单例的核心,通过 synchronized 关键字包管线程安全。
    • 第一次调用时,instance 为 null,会创建一个新的实例;
      后续调用时,直接返回已经创建的实例。

  • 功能性方法 log():

    • 提供详细的业务功能,例如记录日志。


使用示例

假设我们需要记录一些紧张的操纵日志,可以通过以下代码来使用 LogManager:
  1. public class Main {
  2.     public static void main(String[] args) {
  3.         // 第一次调用时实例化 LogManager
  4.         LogManager logger1 = LogManager.getInstance();
  5.         logger1.log("This is the first log message.");
  6.         // 第二次调用时直接返回已有实例
  7.         LogManager logger2 = LogManager.getInstance();
  8.         logger2.log("This is the second log message.");
  9.         // 比较两个实例
  10.         System.out.println("Are logger1 and logger2 the same instance? " + (logger1 == logger2));
  11.     }
  12. }
复制代码

输出结果

  1. LogManager initialized!
  2. Log: This is the first log message.
  3. Log: This is the second log message.
  4. Are logger1 and logger2 the same instance? true
复制代码
说明:



  • 第一次调用 LogManager.getInstance() 时,LogManager 被初始化(输出 "LogManager initialized!")。
  • 第二次调用 getInstance() 时,只是返回已有实例,没有再次创建新实例。
  • 比较两个实例,结果为 true,表明它们是同一个对象。

懒汉式单例的优缺点

优点


  • 延迟加载

    • 实例在第一次使用时才创建,节省内存和体系资源。

  • 线程安全性

    • 使用 synchronized 包管线程安全。

缺点


  • 性能问题

    • 每次调用 getInstance() 都需要进入同步块,会带来一定的性能开销。在性能敏感的场景下可能不敷高效。


改进方案:双重查抄锁定(Double-Checked Locking)

为了办理同步带来的性能问题,可以使用双重查抄锁定优化懒汉式单例:
  1. public class LogManager {
  2.     // 1. 静态变量,使用 volatile 修饰以保证可见性
  3.     private static volatile LogManager instance = null;
  4.     // 2. 私有构造方法
  5.     private LogManager() {
  6.         System.out.println("LogManager initialized!");
  7.     }
  8.     // 3. 提供静态方法,使用双重检查锁定
  9.     public static LogManager getInstance() {
  10.         if (instance == null) { // 第一次检查
  11.             synchronized (LogManager.class) {
  12.                 if (instance == null) { // 第二次检查
  13.                     instance = new LogManager();
  14.                 }
  15.             }
  16.         }
  17.         return instance;
  18.     }
  19.     // 示例功能
  20.     public void log(String message) {
  21.         System.out.println("Log: " + message);
  22.     }
  23. }
复制代码
优势



  • 第一次查抄和第二次查抄减少了不必要的同步,提高了性能。
  • 使用 volatile 关键字包管多线程环境下的可见性,防止指令重排序导致的错误。
扩展1 — 双重查抄锁定

这段代码是一个双重查抄锁定(Double-Checked Locking)实现的懒汉式单例模式的核心部分,它旨在办理多线程环境下单例实例创建的线程安全问题,同时优化性能。下面是对这段代码的详细分析:

  • 第一次查抄 (if (instance == null)):

    • 目的:快速判定实例是否已经创建。
    • 优点:假如实例已经存在,则可以直接返回实例,避免进入同步块,提高了性能。

  • 同步块 (synchronized (LogManager.class)):

    • 目的:包管在多线程环境下只有一个线程能够进入块内创建实例,确保线程安全。
    • synchronized用在类对象上,确保同一时候只有一个线程可以初始化实例。

  • 第二次查抄 (if (instance == null)):

    • 目的:在同步块内再次查抄实例是否为 null。
    • 原因:在第一次查抄之后进入同步块之前,可能有其他线程已经创建了实例,因此需要再次查抄以防止重复创建。

  • 实例化 (instance = new LogManager()):

    • 当且仅当 instance 确实为 null 且当前线程得到了同步锁时,才创建实例。
    • 确保 LogManager 的实例只被创建一次。

  • 返回实例 (return instance):

    • 无论是通过快速路径(无锁)照旧同步路径(加锁),终极都会返回唯一的实例。

Why Double-Checked Locking?


  • 性能优化

    • 通过双重查抄,减少了进入同步锁的次数。只有在 instance 为 null 时,才会进入同步块。一般环境下(即实例已经创建后),只需要执行第一次查抄即可返回实例,无需同步。

  • 线程安全

    • 同步块包管了只有一个线程可以执行实例初始化。纵然多个线程同时发现 instance 为 null,由于同步的存在,终极只有一个线程能够创建实例。

扩展2 — 使用 volatile

为了完全包管双重查抄锁定的精确性,instance 应该使用 volatile 关键字声明:
  1. private static volatile LogManager instance = null;
复制代码


  • 作用

    • 防止指令重排序1:确保 new LogManager() 操纵的顺序精确,即先分配内存,再执行构造函数,最后将内存地址赋值给 instance。
    • 变量在多个线程之间的可见性:一旦一个线程修改了 instance,其他线程能够立即看到变化。

双重查抄锁定模式是实现懒汉式单例的一种高效方式,适用于性能要求较高的多线程环境。但是,它的精确实现依赖于 volatile 关键字来防止重排序问题。通过这种方式,我们可以在包管线程安全的同时,尽量减少同步带来的性能损耗。

总结

懒汉式单例适合于需要延迟加载且实例化成本较高的场景(如日志管理器、配置加载器等)。在并发场景下,最好使用线程安全的实现,例如同步方法版或双重查抄锁定版,以确保唯一实例的精确性和性能的平衡。

   

  • 指令重排序:编译器和处理器在执行程序时可能会对指令进行重排序,以优化性能。volatile 关键字会禁止这种重排序,确保变量的初始化和其他操纵的执行顺序符合程序的预期。
    这对于实现线程安全的懒汉式单例模式非常紧张,因为它包管了对象在初始化完成后才会被其他线程看到。 ↩︎

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表