JavaRush /Java 博客 /Random-ZH /设计模式:单例

设计模式:单例

已在 Random-ZH 群组中发布
你好!今天我们来仔细看看不同的设计模式,我们先从 Singleton 模式开始,也称为“单例”。 设计模式:单例 - 1让我们记住:我们对设计模式总体了解多少?设计模式是解决许多已知问题的最佳实践。设计模式通常不依赖于任何编程语言。将它们作为一组建议,遵循这些建议,您可以避免错误,而不是重新发明轮子。

什么是单例?

单例是可应用于类的最简单的设计模式之一。人们有时会说“这个类是单例”,意思是这个类实现了单例设计模式。有时需要编写一个只能创建一个对象的类。例如,负责记录或连接数据库的类。单例设计模式描述了我们如何完成这样的任务。单例是一种做两件事的设计模式:
  1. 保证一个类只有一个该类的实例。

  2. 提供对该类的实例的全局访问点。

因此,几乎每个单例模式的实现都有两个特征:
  1. 私有构造函数。限制在类本身之外创建类对象的能力。

  2. 返回类的实例的公共静态方法。这种方法称为getInstance. 这是类实例的全局访问点。

实施方案

单例设计模式有不同的使用方式。每个选项都有其自身的优点和缺点。在这里,一如既往:没有理想,但需要为之奋斗。但首先,让我们定义什么是好的,什么是坏的,以及哪些指标会影响设计模式的实现评估。让我们从积极的方面开始。以下是赋予实施多汁性和吸引力的标准:
  • 延迟初始化:当应用程序恰好在需要时加载类时。

  • 代码的简单性和透明度:当然,衡量标准是主观的,但很重要。

  • 线程安全:在多线程环境下正常工作。

  • 多线程环境中的高性能:共享资源时,线程之间的阻塞最少或根本不阻塞。

现在说说缺点。我们列出了显示实施情况不佳的标准:
  • 非惰性初始化:当应用程序启动时加载一个类,无论是否需要它(一个悖论,在IT世界中,最好是惰性的)

  • 代码复杂且可读性差。该指标也是主观的。我们假设如果血液来自眼睛,那么实施效果一般。

  • 缺乏线程安全。换句话说,“线程危险”。多线程环境下操作不正确。

  • 多线程环境中的性能较差:线程在共享资源时始终或经常相互阻塞。

代码

现在我们准备考虑各种实施选项,列出优点和缺点:

简单的解决方案

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
最简单的实现。优点:
  • 代码简单透明

  • 线程安全

  • 多线程环境下的高性能

缺点:
  • 不是惰性初始化。
为了纠正最后一个缺陷,我们得到了第二个实现:

延迟初始化

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
优点:
  • 延迟初始化。

缺点:
  • 不是线程安全的

实施很有趣。我们可以延迟初始化,但是我们失去了线程安全。没问题:在第三个实现中,我们同步所有内容。

同步访问器

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
优点:
  • 延迟初始化。

  • 线程安全

缺点:
  • 多线程环境下性能不佳

伟大的!在第三个实现中,我们恢复了线程安全!确实,很慢……现在方法getInstance是同步的,一次只能输入一个。事实上,我们不需要同步整个方法,而只需要同步初始化新类对象的部分。但我们不能简单地将synchronized负责创建新对象的部分包装在块中:这不会提供线程安全性。情况有点复杂。正确的同步方法如下:

双重检查锁定

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
优点:
  • 延迟初始化。

  • 线程安全

  • 多线程环境下的高性能

缺点:
  • 低于 1.5 的 Java 版本不支持(1.5 版中修复了 volatile 关键字)

我注意到,要使此实施选项正常工作,需要满足以下两个条件之一。该变量INSTANCE必须是final, 或volatile。我们今天要讨论的最后一个实现是Class Holder Singleton.

类持有者单例

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
优点:
  • 延迟初始化。

  • 线程安全。

  • 多线程环境下的高性能。

缺点:
  • 为了正确运行,需要保证类对象的Singleton初始化没有错误。否则,第一个方法调用getInstance将以错误结束ExceptionInInitializerError,并且所有后续方法调用都将失败NoClassDefFoundError

实施几乎是完美的。而且是惰性的、线程安全的、快速的。但负号中描述了一个细微差别。 Singleton模式的各种实现对比表:
执行 延迟初始化 线程安全 多线程速度 什么时候使用?
简单的解决方案 - + 快速地 绝不。或者当延迟初始化不重要时。但永远不会更好。
延迟初始化 + - 不适用 总是在不需要多线程时
同步访问器 + + 慢慢地 绝不。或者当多线程工作的速度并不重要时。但永远不会更好
双重检查锁定 + + 快速地 在极少数情况下,您需要在创建单例时处理异常。(当 Class Holder Singleton 不适用时)
类持有者单例 + + 快速地 总是在需要多线程并且保证创建单例类对象时不会出现问题。

单例模式的优点和缺点

一般来说,单例完全按照预期执行:
  1. 保证一个类只有一个该类的实例。

  2. 提供对该类的实例的全局访问点。

然而,这种模式有缺点:
  1. Singleton 违反了 SRP(单一职责原则)——Singleton 类除了其直接职责外,还控制其副本数量。

  2. 常规类或方法对单例的依赖在类的公共契约中不可见。

  3. 全局变量是不好的。单例最终会变成一个巨大的全局变量。

  4. 单例的存在通常降低了应用程序的可测试性,特别是使用单例的类的可测试性。

好吧,现在一切都结束了。我们研究了单例设计模式。现在,在与程序员朋友的终生对话中,您不仅可以说出它的优点,还可以说出它的缺点。祝你掌握新知识好运。

补充阅读:

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION