什么是单例?
单例是可应用于类的最简单的设计模式之一。人们有时会说“这个类是单例”,意思是这个类实现了单例设计模式。有时需要编写一个只能创建一个对象的类。例如,负责记录或连接数据库的类。单例设计模式描述了我们如何完成这样的任务。单例是一种做两件事的设计模式:-
保证一个类只有一个该类的实例。
-
提供对该类的实例的全局访问点。
-
私有构造函数。限制在类本身之外创建类对象的能力。
-
返回类的实例的公共静态方法。这种方法称为
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 违反了 SRP(单一职责原则)——Singleton 类除了其直接职责外,还控制其副本数量。
-
常规类或方法对单例的依赖在类的公共契约中不可见。
-
全局变量是不好的。单例最终会变成一个巨大的全局变量。
-
单例的存在通常降低了应用程序的可测试性,特别是使用单例的类的可测试性。
GO TO FULL VERSION