跟着GPT学设计模式之单例模式

单例设计模式(Singleton Design Pattern)一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

单例有几种经典的实现方式,它们分别是:饿汉式、懒汉式、双重检测、静态内部类、枚举。

单例模式解决的问题

  • 处理资源访问冲突,资源的访问因为并发带来的问题。通过限制类的实例化过程,单例模式确保在应用程序运行期间只会创建一个特定类的对象。这对于需要共享数据或资源的情况非常有用,避免了多个实例同时操作导致数据不一致或资源浪费的问题。
  • 表示全局唯一类,比如,配置信息类。单例模式提供了一个全局访问点,使得其他类可以轻松地访问该单例对象。这样可以方便地共享类的实例,避免了频繁地传递对象实例的麻烦。

带来的好处:

  • 在一些场景中能减少内存的使用和性能提升。
  • 解决资源访问互斥的问题。
  • 节省资源:由于单例模式只创建一个实例,可以节省系统资源,尤其是在需要频繁创建和销毁对象的场景下。
  • 维护一致性:单例模式可以确保数据和资源的一致性,因为只有一个实例进行操作,避免了多个实例之间的竞争和冲突。
  • 全局访问:单例模式提供了一个全局访问点,使得其他对象可以轻松地获取实例,方便了数据共享和交互。
  • 延迟实例化:单例模式可以延迟对象的实例化,只有在需要时才进行创建,提高了系统的性能和效率。

缺点:

  • 高耦合性:单例模式的实现通常需要在类中创建全局访问点,这导致了对象的使用者与单例类之间存在高度的耦合性。这样一来,当需要修改单例类时,可能需要修改引用该类的所有代码,增加了代码的维护难度。
  • 难以进行单元测试:由于单例模式在全局范围内共享实例,很难对单例对象进行模拟和替换,从而使得单元测试变得困难。单元测试应该是隔离的、独立的,而单例模式的全局特性会影响到测试结果,增加了测试的复杂性。
  • 对象生命周期过长:单例模式的对象在整个应用程序的生命周期中都存在,无法自动释放和回收。如果不正确地使用和管理单例对象,可能会导致内存泄漏或资源浪费的问题。
  • 不支持多线程并发访问:某些实现方式的单例模式在多线程环境下可能会出现并发访问的问题,需要进行额外的线程安全处理。例如,懒汉式需要在获取实例时进行同步处理,可能会影响性能。
  • 违反单一职责原则:单例模式通常承担了过多的职责,既要负责自身的逻辑功能,又要管理对象的生命周期和资源等。这样违反了单一职责原则,降低了代码的可读性和可维护性。

单例模式的实现

饿汉式

饿汉式是一种单例模式的实现方式,其特点是在类加载时就创建并初始化了单例对象,无论是否需要使用该对象。

  1. 类加载时即创建对象:在饿汉式中,单例对象的创建和初始化发生在类加载的过程中,因此在应用程序启动时就已经存在一个单例对象。
  2. 线程安全:饿汉式的实现方式保证了在多线程环境下的线程安全性。由于单例对象在类加载时就被创建,所以不存在多个线程同时访问和创建对象的情况,避免了并发访问导致的线程安全问题。
  3. 全局访问点:饿汉式通过静态变量提供了一个全局访问点,其他对象可以直接通过该变量获取单例对象,方便了对单例对象的使用和操作。
  4. 性能优化:饿汉式避免了每次获取单例对象时的实例化开销,因为对象在类加载时已经完成了实例化。这在某些场景下可以提高系统性能。

然而,饿汉式也存在一些缺点:

  1. 占用内存空间:由于在类加载时就创建了单例对象,所以该对象会一直存在于内存中,无论是否被使用。如果单例对象占用较大内存,可能会造成资源浪费。
  2. 强耦合性:饿汉式在类加载时就创建对象,导致单例对象与类的生命周期紧密耦合,难以灵活控制单例对象的创建和销毁。
  3. 延迟加载不支持:饿汉式无法实现延迟加载,即只有当需要使用单例对象时才进行实例化。这可能在某些场景下造成不必要的开销。

因此,在使用饿汉式时需要考虑以上优缺点,并根据具体需求进行选择。

package com.iluwatar.singleton;

/**
 * Singleton class. Eagerly initialized static instance guarantees thread safety. 单例类,在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。
 */
public final class IvoryTower {

  /**
   * Private constructor so nobody can instantiate the class.
   * 构造器私有,所以无法通过构造器创建这个类
   */
  private IvoryTower() {
  }

  /**
   * Static to class instance of the class.
   * 静态变量初始化,jvm加载的时候就会执行
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * To be called by user to obtain instance of the class.
   * 通过这个方法获取这个类的实例
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    return INSTANCE;
  }
}

懒汉式

懒汉式是单例模式的另一种实现方式。与饿汉式不同,懒汉式在需要时才进行单例对象的创建和初始化,也就是延迟加载。

以下是懒汉式的一般实现方式:

  • 私有化构造方法:将单例类的构造方法声明为私有,防止外部代码通过直接实例化来创建对象。
  • 提供静态方法获取实例:通过一个静态方法来获取单例对象,在该方法内部进行懒加载的处理。
  • 延迟加载:在静态方法内部,先判断单例对象是否已经被创建,如果未创建,则进行对象的实例化操作。否则,直接返回已创建的对象。
  • 线程安全处理:因为懒汉式在需要时才创建对象,所以存在多线程环境下可能同时进入创建实例的判断逻辑的情况。因此,需要在静态方法中增加同步控制,保证只有一个线程可以创建实例。

懒汉式相对于饿汉式有一些优点:

  • 延迟加载:懒汉式可以在需要时才进行实例化,避免了在应用程序启动时就创建对象,从而减少了不必要的资源消耗。
  • 线程安全控制:通过在静态方法中增加同步控制,可以保证在多线程环境下的线程安全性。

然而,懒汉式也存在一些缺点:

  • 性能开销:由于需要进行同步控制,懒汉式在获取单例对象时可能会引入性能开销,特别是在高并发环境下。
  • 双重检查锁机制问题:懒汉式通常使用双重检查锁机制来实现延迟加载和线程安全。但这种方式在某些编程语言和平台下可能存在问题,导致无法正确实现线程安全性。

因此,在使用懒汉式时需要注意解决线程安全性问题,并根据具体需求和场景综合考虑是否选择懒汉式作为单例模式的实现方式。


/**
 * <p>Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization
 * mechanism.</p>懒汉式相对于饿汉式的优势是支持延迟加载。增加了类锁。
 *
 */
public final class ThreadSafeLazyLoadedIvoryTower {

  private static volatile ThreadSafeLazyLoadedIvoryTower instance;

  private ThreadSafeLazyLoadedIvoryTower() {
    // Protect against instantiation via reflection
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * The instance doesn't get created until the method is called for the first time.实例不能初始化直到第一次调用完成,因为加了锁的。
   */
  public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {
    if (instance == null) {
      instance = new ThreadSafeLazyLoadedIvoryTower();
    }
    return instance;
  }
}

双重检测

双重检测(Double-Checked Locking)是一种在懒汉式中用于实现延迟加载和线程安全的机制。它是为了解决懒汉式在多线程环境下可能出现竞态条件(Race Condition)而引入的。

双重检测的基本原理如下:

  • 在静态方法中首先进行一次判断,如果单例对象已经被创建,则直接返回该对象,避免不必要的同步开销。
  • 如果单例对象尚未创建,则进入同步块。
  • 在同步块内部再次进行判断,确保只有一个线程可以创建单例对象。
  • 在同步块内部创建单例对象,并将其赋值给静态变量。
  • 最后,返回单例对象。

通过双重检测机制,可以减少对同步锁的使用,提高性能。

需要注意的是,在某些编程语言和平台下,双重检测锁机制可能存在问题,即所谓的双重检测锁失效问题,导致无法正确实现线程安全性。为了解决这个问题,可以使用volatile关键字来确保可见性,或者使用其他线程安全的机制来实现延迟加载。

总之,双重检测是一种在懒汉式中常用的实现方式,可以在一定程度上解决线程安全和性能的问题,但需要注意在具体的编程语言和平台上是否适用,并仔细考虑是否需要额外的机制来确保线程安全性。


/**
 * <p>Double check locking.</p>
 * 既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式
 * <p>http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html</p>
 *
 * <p>Broken under Java 1.4.</p>
 *
 * @author mortezaadi@gmail.com
 */
public final class ThreadSafeDoubleCheckLocking {

  private static volatile ThreadSafeDoubleCheckLocking instance;

  /**
   * private constructor to prevent client from instantiating.构造器私有防止客户端调用初始化
   */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call抛出异常来防止反射调用
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * Public accessor.
   *
   * @return an instance of the class.
   */
  public static ThreadSafeDoubleCheckLocking getInstance() {
    // local variable increases performance by 25 percent
    // Joshua Bloch "Effective Java, Second Edition", p. 283-284

    var result = instance;
    // Check if singleton instance is initialized. 检查单例是否初始化,如果初始化了就直接返回
    // If it is initialized then we can return the instance.
    if (result == null) {
      // It is not initialized but we cannot be sure because some other thread might have
      // initialized it in the meanwhile. 多线程并发可能同时创建实例,这里需要加锁。
      // So to make sure we need to lock on an object to get mutual exclusion.
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        // Again assign the instance to local variable to check if it was initialized by some
        // other thread while current thread was blocked to enter the locked zone.
        // If it was initialized then we can return the previously created instance
        // just like the previous null check. 双重检查机制,如果创建了则直接返回
        result = instance;
        if (result == null) {
          // The instance is still not initialized so we can safely
          // (no other thread can enter this zone)
          // create an instance and make it our singleton instance. 如果仍然没有创建则我们可以在此处创建一个对象并赋值。
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

静态内部类

在单例模式中,使用静态内部类实现是一种常见且线程安全的方式。通过静态内部类的特性,可以实现延迟加载和线程安全的单例对象。通过静态内部类实现的单例模式具有延迟加载、线程安全和高效的特点。在需要使用单例对象的时候才会进行实例化,而且能够保证多线程环境下的线程安全性。


/**
 * <p>The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton
 * object in Java.</p>Initialize on demand holder习惯用法是在Java中创建延迟初始化的单例对象的一种安全方法。
 *
 * <p>The technique is as lazy as possible and works in all known versions of Java. It takes
 * advantage of language guarantees about class initialization, and will therefore work correctly
 * in all Java-compliant compilers and virtual machines.</p>
 * 该技术尽可能懒惰,适用于所有已知版本的Java。它利用了关于类初始化的语言保证,因此将在所有符合Java的编译器和虚拟机中正确工作。
 * <p>The inner class is referenced no earlier (and therefore loaded no earlier by the class loader)
 * than the moment that getInstance() is called. Thus, this solution is thread-safe without
 * requiring special language constructs (i.e. volatile or synchronized).</p>
 *
 */
public final class InitializingOnDemandHolderIdiom {

  /**
   * Private constructor.
   */
  private InitializingOnDemandHolderIdiom() {
  }

  /**
   * Singleton instance.
   *
   * @return Singleton instance
   */
  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

  /**
   * Provides the lazy-loaded Singleton instance.
   */
  private static class HelperHolder {
    private static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }
}

枚举

使用枚举实现单例模式的优点包括:

  • 简洁明了:枚举实现单例模式非常简洁,只需声明一个枚举值,即可获得唯一实例。
  • 线程安全:枚举类型的实例是在类加载时初始化的,因此保证了线程安全性。
  • 序列化和反序列化安全:枚举类默认实现了Serializable接口,因此枚举单例在进行序列化和反序列化时,能够正确地保持实例的唯一性。
/**
 * <p>Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18</p>
 *
 * <p>This implementation is thread safe, however adding any other method and its thread safety
 * is developers responsibility.</p>这种创建单例的方法是线程安全的,但是添加其他方法的线程安全问题需要开发者处理。
 */
public enum EnumIvoryTower {

  INSTANCE;

  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }
}

以上内容基于GPT创建和整理。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值