设计模式——单例模式

用过Spring的都知道bean是单例,来看看单例模式的各种实现方式。

单例模式

对于系统中的某些类来说,只有一个实例很重要。单例模式就是让类自身负责保存它的唯一实例,这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。

角色

  1. Singleton(单例角色)

    在单例类的内部实现只生成一个实例,同时提供一个静态的getInstance()工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,构造方法设为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

类图

image.png

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class IDCardNumber {
private static IDCardNumber instance = null;
private String number;

private IDCardNumber() {}

public static IDCardNumber getInstance() {
if (instance == null) {
System.out.println("First Application for id card, get a new number.");
instance = new IDCardNumber();
instance.setNumber("No10086");
} else {
System.out.println("Duplicate application for id card, get the old number.");
}
return instance;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}
}

客户端测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {
public static void main(String[] args) {
IDCardNumber card_1 = IDCardNumber.getInstance();
IDCardNumber card_2 = IDCardNumber.getInstance();
System.out.println("Are the id cards the same: " + ((card_1 == card_2) ? "yes" : "no"));

String id_1 = card_1.getNumber();
String id_2 = card_2.getNumber();
System.out.println("The first number: " + id_1);
System.out.println("The second number: " + id_2);
System.out.println("Are the id numbers the same: " + ((id_1.equals(id_2)) ? "yes" : "no"));
System.out.println("Are these two String objects the same: " + ((id_1 == id_2) ? "yes" : "no"));
}
}

运行结果为

1
2
3
4
5
6
7
First Application for id card, get a new number.
Duplicate application for id card, get the old number.
Are the id cards the same: yes
The first number: No10086
The second number: No10086
Are the id numbers the same: yes
Are these two String objects the same: yes

实现单例模式的几种方式

懒汉式(线程不安全)

上述实例中看到的即为懒汉式,且线程不安全,所以不能算作真正意义上的单例。

是否懒(Lazy)初始化:是

是否线程安全:否

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {  
private static Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

懒汉式(线程安全)

在线程不安全的懒汉式基础上加锁以实现线程安全,但效率很低。

是否懒(Lazy)初始化:是

是否线程安全:是

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {  
private static Singleton instance;

private Singleton() {}

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

饿汉式

饿汉式由于没有加锁,所以效率会高很多,它基于classloader机制避免了多线程的同步问题,但instance在类装载时就被实例化,故无法达到懒初始化的效果。

是否懒(Lazy)初始化:否

是否线程安全:是

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static Singleton instance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

双检锁/双重校验锁(DCL,double-checked locking)

该方式从JDK 1.5之后支持,采用双锁机制,是线程安全的,并能在多线程情况下保持高性能。

是否懒(Lazy)初始化:是

是否线程安全:是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {
// 用volatile关键字修饰该变量,JVM会把线程本地内存中的变量强制刷新到主内存中
// 由于创建实例并不是一个原子的指令,该过程中可能发生指令重排,而volatile关键字还可以避免指令重排
private volatile static Singleton singleton;

private Singleton() {}

public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

登记式/静态内部类

这种方式以更简单的实现方式达到了双检锁方式的效果,同样是利用classloader机制解决多线程的同步问题,是对饿汉式的优化。在饿汉式中,只要Singleton类被装载,则instance就会被实例化,但在该方式中,Singleton类被装载后不会同时将instance实例化,而是等待getInstance()方法被调用后,才会显示装载SingletonHolder类,从而达到了懒初始化的效果。

是否懒(Lazy)初始化:是

是否线程安全:是

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton() {}

public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

枚举

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。JDK 1.5之后才加入了enum特性,实际中较少用到。

是否懒(Lazy)初始化:否

是否线程安全:是

1
2
3
4
5
public enum Singleton {  
INSTANCE;

public void whateverMethod() {}
}

一般情况下不建议使用懒汉式,建议使用饿汉式;只有在明确要实现懒初始化的情况下才使用登记式/静态内部类;若涉及到反序列化创建对象时,可以使用枚举方式;如果有其他特殊需求,也可考虑双检锁/双重校验锁方式。

模式优缺点

优点

  • 提供了对唯一实例的受控访问;
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统的性能;
  • 允许可变数目的实例,基于单例模式可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。

缺点

  • 由于单例模式没有抽象层,因此单例类的扩展有很大难度;
  • 单例类的职责过重,在一定程度上违背了单一职责原则;
  • 滥用单例将带来一些负面问题,例如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如Java、C#等拥有自动垃圾回收机制的语言,会将长时间未被利用的单例对象回收,将导致对象状态的丢失。

Spring框架中,当我们试图从Spring容器中获取某个类的实例时,默认情况下Spring会以单例模式创建实例。

设计模式——单例模式

https://deleter-d.github.io/posts/25281/

作者

亦初

发布于

2022-06-21

更新于

2024-06-19

许可协议

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...