设计模式 单例模式

本文将介绍设计模式中的单例模式。

一、什么是单例模式?

确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

二、实现

1. 结构

2. 角色

  • Singleton 单例:
    • 在类的内部持有该类的唯一实例
    • 提供静态方法,允许外界获取实例

3. 要点

  • 私有化构造函数,不允许外部通过 new 创建实例
  • 将实例以静态的、私有的类成员的方式,维护在类中
  • 提供一个静态的、共有的实例获取函数

三、单例的写法

1. 饿汉式与懒汉式

  • 懒汉式:有事不急着做,等火烧眉毛了再做
  • 饿汉式:有事一来就先做了

2. 不安全的懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 静态私有成员变量
private static Singleton instance = null;

// 私有构造函数
private Singleton {}

// 获取实例方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这个方法实际上是线程不安全的,如果在最开始时有多个线程同时尝试获取该实例,多个线程可能会获取到多个不同实例。

3. 饿汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
// 静态私有成员变量
private static Singleton instance = new Singleton();

// 私有构造函数
private Singleton {}

// 获取实例方法
public static Singleton getInstance() {
return instance;
}
}

在定义静态变量时直接将其实例化,当类的静态方法被调用前,单例就已经创建。

Singleton.getInstance() 被执行时,Singleton 类会被加载进虚拟机,静态成员变量会被创建并复制,这个过程会在类加载阶段执行,并且虚拟机会保证其线程安全。

因此,在执行 Singleton.getInstance() 之前,实例就已经存在。

4. 懒汉式单例 + synchronized

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 静态私有成员变量
private static Singleton instance = null;

// 私有构造函数
private Singleton {}

// 获取实例方法
synchronized public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

通过在方法上增加 synchronized 关键字,能够保证同一时刻只有一个线程能够访问此方法,从而避免了上面的线程不安全问题。

但此方法的性能实际上是较差的,因为同一时刻只能有一个线程能够访问该方法,也意味着同一时刻只能有一个线程获取实例。

5. 懒汉式单例 + 细粒度锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
// 静态私有成员变量
private static Singleton instance = null;

// 私有构造函数
private Singleton {}

// 获取实例方法
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}

将加锁的粒度缩小至实例化时,不仅可以保证同一时刻只能有一个线程尝试实例化,也不会影响多个线程对实例的获取。

但这一方法存在线程安全问题:假如多个线程同时访问该方法,发现 instance 为 null,它们会尝试加锁、陆续获取到锁、进行实例化,导致系统中出现多个实例。

6. 懒汉式单例 + 细粒度锁 + 双重检查 + volatile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
// 静态私有成员变量
private volatile static Singleton instance = null;

// 私有构造函数
private Singleton {}

// 获取实例方法
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

主要进行了两个工作:

  • 双重检查:当成功获取到锁后,检查实例状态,避免 “陆续获取到锁” 导致的重复实例化
  • 增加 volatile 关键字:禁用 CPU 缓存,使 CPU 读取该变量时从内存中读取

四、优缺点

1. 优点

  • 限定了外界对实例的获取入口,因此可以方便地控制外界对实例的获取方式、时机
  • 可以节省系统资源

2. 缺点

  • 单例类的职责过重,既负责业务功能,也负责提供对象获取
  • 现在许多语言都提供了自动垃圾回收功能,如果单例长时间不被利用,它可能会被销毁并回收

参考