单例模式
2024-10-09  / 23 种设计模式  / 1. 创建型模式

0. 概述

单例模式(Singleton Pattern) 是一种创建型设计模式,确保一个类有且只有 一个实例,并提供 一个全局访问点 来获取该单例实例。

维基百科:

在软件工程中,单例模式是一种软件设计模式,它限制一个类只能有一个实例。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

关键特点

  • 单一实例: 一个类只有一个实例。
  • 全局访问点: 提供一个全局访问点来获取该实例。

基本结构

  • 私有构造函数: 构造函数被设为私有,确保该类无法在类外部被实例化。
  • 私有静态字段: 一个私有静态字段 instance 用来保存该类的唯一实例,并且该实例应在类内部创建。
  • 公共静态方法: 一个公共静态方法 getInstance(),通常作为全局访问点,用来返回该类的单例实例。

优点

  • 只有一个单一实例。
  • 提供一个全局可访问的单一访问点。
  • 对于管理共享资源非常有用。
  • 防止频繁创建和销毁实例,从而减少内存开销。
  • 通过使用单一实例确保一致性。
  • 基于延迟初始化(Lazy Initialization),实例可以在首次访问时创建。

缺点

  • 不够灵活:它会使代码与特定实例紧密耦合,从而降低灵活性。
  • 通常被标记为 final,或者以不能被继承的方式设计。
  • 不适用于可变对象。如果相同类型的对象在不同使用场景中需要发生变化,容易导致数据错误。
  • 如果功能设计不合理,可能违反单一职责原则
  • 使单元测试变得具有挑战性。

几种单例模式实现的比较

类型 线程安全性 加载方式 推荐度 反射攻击 序列化攻击 完美版本
1.1. 懒汉式单例 - 线程不安全 × 延迟加载 - - -
1.2. 懒汉式单例 - synchronized √ (同步静态方法) 延迟加载 - - -
1.3. 懒汉式单例 - 延迟锁 × 延迟加载 - - -
1.4. 懒汉式单例 - 双重检查锁定(无 volatile) × 延迟加载 - - -
1.5. 懒汉单例 - 双重检查锁定(DCL) √ (volatile 实例 + DCL) 延迟加载 (DCL) 👍 × (1.5.1. 对“懒汉单例 - 双重检查锁定”的反射攻击) √ (1.5.2. 对“懒汉单例 - 双重检查锁定”的序列化攻击) 1.5.3. 完美版的“懒汉单例 - 双重检查锁定”
2.1. 饿汉式单例 - 静态常量 √ (类加载机制) 饿汉式(类加载时) 👍 √ (2.1.1. 对“饿汉式单例 - 静态常量”的反射攻击) √ (2.1.2. 对“饿汉式单例 - 静态常量”的序列化攻击) 2.1.3. 完美版的“饿汉式单例 - 静态常量”
2.2. 饿汉式单例 - 静态块 √ (类加载机制) 饿汉式(类加载时) 👍 √ (2.2.1. 对“饿汉式单例 - 静态块”的反射攻击) √ (2.2.2. 对“饿汉式单例 - 静态块”的序列化攻击) 2.2.3. 完美版本的“饿汉式单例 - 静态块”
3. 静态内部类单例 √ (类加载机制) 延迟加载(静态内部类) 👍 √ (3.1. 对“静态内部类单例”的反射攻击) √ (3.2. 对“静态内部类单例”的序列化攻击) 3.3. 完美版本的“静态内部类单例”
4. 枚举型单例 √ (天生安全,类加载机制) 饿汉式(枚举类加载时) 👍 √ (4.1. 对“枚举型单例”的反射攻击) √ (4.2. 对“枚举型单例”的序列化攻击) 4.3. 完美版本的“枚举型单例”

1. 懒汉式单例

懒加载意味着实例只会在首次访问或首次调用静态方法时创建。

1.1. 懒汉式单例 - 线程不安全

它在单线程环境下工作正常,但在多线程环境中可能会破坏单例模式,导致创建多个不同的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LazySingletonThreadUnsafe {

// 持有单例实例
private static LazySingletonThreadUnsafe instance;

// 私有构造函数:限制用户自行创建实例
private LazySingletonThreadUnsafe() {
}

// 控制对单例实例的访问
public static LazySingletonThreadUnsafe getInstance() {
if (null == instance) {
/*try {
// 模拟多线程环境下无法保证单例模式的情况,
// 提高重现问题的概率。
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
instance = new LazySingletonThreadUnsafe();
}
return instance;
}
}

测试代码:

  • 单线程环境:可以获取相同的实例。
1
2
3
4
5
6
7
8
9
@Test
public void testLazySingletonThreadUnsafeSingleThread() {
LazySingletonThreadUnsafe instance1 = LazySingletonThreadUnsafe.getInstance();
LazySingletonThreadUnsafe instance2 = LazySingletonThreadUnsafe.getInstance();
System.out.println("instance1: " + instance1);
System.out.println("instance2: " + instance2);
// `instance1` 和 `instance2` 是相同的实例。
Assertions.assertSame(instance1, instance2); // √
}
  • 多线程环境:可能会获取到不同的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testLazySingletonThreadUnsafeMultiThread() throws ExecutionException, InterruptedException {
final int THREAD_COUNT = 100;
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);

Callable<LazySingletonThreadUnsafe> callableTask = () -> LazySingletonThreadUnsafe.getInstance();
Future<LazySingletonThreadUnsafe>[] futures = new Future[THREAD_COUNT];

for (int i = 0; i < THREAD_COUNT; i++) {
futures[i] = executorService.submit(callableTask);
}

LazySingletonThreadUnsafe firstInstance = futures[0].get();
for (Future<LazySingletonThreadUnsafe> future : futures) {
LazySingletonThreadUnsafe instance = future.get();
System.out.println(instance);
// 在多线程环境中可能会破坏单例模式,导致获取到不同的实例。
// 如果问题无法重现,请尝试多次运行测试方法或取消注释 LazySingletonThreadUnsafe.getInstance() 中的代码。
Assertions.assertSame(firstInstance, instance); // ×
}

executorService.shutdown();
}

1.2. 懒汉式单例 - synchronized

1.1. 懒汉式单例 - 线程不安全 不支持多线程场景。我们可以简单粗暴地在 getInstance() 方法上添加 synchronized 锁。虽然这种方法可以解决线程安全问题,但效率低下,因为它通过锁定静态方法来锁定类对象(类级别锁,而不是对象级别锁)。这意味着每次多个线程尝试通过 getInstance() 方法访问单例实例时,都会被锁定,只有一个线程能够访问,这大大影响了性能。实际上,这种方法只需在类实例化时执行一次,之后的所有请求都可以直接返回已经实例化的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LazySingletonSynchronized {

// 持有单例实例
private static LazySingletonSynchronized instance;

// 私有构造函数:限制用户自行创建实例
private LazySingletonSynchronized() {
}

// 同步静态方法:类级别锁确保同一时间只有一个线程可以执行静态同步方法。
public synchronized static LazySingletonSynchronized getInstance() {
if (null == instance) {
/*try {
// 模拟多线程环境下,多个线程并不总是获取到相同的单例实例,
// 提高重现问题的概率。
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
instance = new LazySingletonSynchronized();
}
return instance;
}
}

1.3. 懒汉式单例 - 延迟锁

基于 1.2. 懒汉式单例 - synchronized,我们可以考虑仅在第一次返回实例时锁定实例化过程。实例在临界区内被实例化,以确保多线程环境中的线程安全。之后的所有操作将直接返回该实例,而不进入临界区,以提高多线程性能。

但是,在多线程环境中仍然会存在线程安全问题。例如,在以下场景中,线程 T1 和 T2 可能会创建两个不同的实例。

  1. T1:刚进入临界区,尚未实例化:instance=null,T1 持有类级别锁
  2. T2:进入 if 代码块,因为 T1 中的 instance 字段尚未实例化完成(instance 仍然为 null):instance=null,T1 持有类级别锁,T2 在进入临界区前被阻塞
  3. T1:完成实例化并退出临界区:instance=LazySingletonDelayLock@4a87761dinstance!=null),T1 释放锁,T2 有机会获得锁
  4. T2:进入临界区并再次执行实例化:instance=LazySingletonDelayLock@2db7a79b(现在它返回了两个不同的实例,破坏了单例模式!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LazySingletonDelayLock {

// 持有单例实例
private static LazySingletonDelayLock instance;

// 私有构造函数:限制用户自行创建实例
private LazySingletonDelayLock() {
}

// 在 `instance` 为空时的 if 代码块中延迟锁定
public static LazySingletonDelayLock getInstance() {
if (null == instance) {
// T2:此时仍可能进入 if 代码块,因为 T1 中的 `instance` 字段尚未完成实例化(`instance` 仍然为 null)
synchronized (LazySingletonDelayLock.class) { // 类级别锁
// T1:刚进入临界区,尚未实例化
instance = new LazySingletonDelayLock();
}
}
return instance;
}
}

1.4. 懒汉式单例 - 双重检查锁定(无 volatile)

为了解决 1.3. 懒汉式单例 - 延迟锁 中提到的问题,我们进一步在同步块中添加检查,确认 instance 是否为空。如果为空,则进行实例化。这可以防止多线程场景下导致的多次实例化问题。

然而,由于 instance 字段没有标记为 volatile,仍然可能由于指令重排序导致错误。

在创建新对象(如 instance = new LazySingletonNoVolatileDoubleCheck())时,内部实际上涉及三个主要步骤:1. 内存分配2. 初始化3. 引用赋值

为了提高性能,JVM 允许对指令进行重排序,这三个步骤可能会被重新排列,如 1. 内存分配3. 引用赋值2. 初始化,这可能会导致线程安全问题:

  1. T1:进入临界区并准备 new
  2. T1:内存分配
  3. T1:引用赋值(因为此时尚未完成初始化,尽管 instance 现在指向新分配的堆空间,但对象只是部分初始化。)
  4. T2:在第一次 if 检查时,发现 instance 不为空(T1 尚未完成完整初始化),直接返回了部分初始化的对象,导致程序错误。
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
26
class LazySingletonNoVolatileDoubleCheck {

// 持有单例实例
private static LazySingletonNoVolatileDoubleCheck instance;

// 私有构造函数:限制用户自行创建实例
private LazySingletonNoVolatileDoubleCheck() {
}

// 添加双重检查锁定,但未标记 `volatile` 的 `instance`
public static LazySingletonNoVolatileDoubleCheck getInstance() {
if (null == instance) { // T2:直接返回 `instance`,但此时尚未完全初始化,导致返回了部分构造或未初始化的实例
synchronized (LazySingletonNoVolatileDoubleCheck.class) {
if (null == instance) {
instance = new LazySingletonNoVolatileDoubleCheck();
// 字节码:1. 内存分配 -> 2. 初始化 -> 3. 引用赋值
// 可能重排序如下:
// 1. 内存分配
// 3. 引用赋值 // T1:`instance` 被赋予引用,但尚未完全初始化
// 2. 初始化
}
}
}
return instance;
}
}

我们已经发现,创建一个新对象的过程中包含了多个步骤。接下来,让我们从 Java 字节码的角度深入理解对象的创建过程。首先,我们编写如下简单的测试代码来创建一个对象:

1
2
3
4
5
public class NewTest {
public static void main(String[] args) {
NewTest newTest = new NewTest();
}
}

我们可以通过 javac -g NewTest.java 编译 NewTest.java 以获取 NewTest.class。然后,我们可以执行 javap -v -p NewTest.class 来获取类文件中字段、构造函数和方法的字节码信息。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Classfile /DesignPattern-Lab/src/main/java/com/sissilab/dp/ox1_creational/ox11_singleton/NewTest.class
Last modified 25/07/2024; size 487 bytes
MD5 checksum e132b23dc7ec6394be9137ad5aa7c33a
Compiled from "NewTest.java"
public class com.sissilab.dp.ox1_creational.ox11_singleton.NewTest
minor version: 0
major version: 52 // Java 主版本号 52 表示用 JDK 1.8(Java 8)编译的类文件。
flags: ACC_PUBLIC, ACC_SUPER // 类访问修饰符:该类是 public(公共的)。
Constant pool:
#1 = Methodref #4.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // com/sissilab/dp/ox1_creational/ox11_singleton/NewTest
#3 = Methodref #2.#19 // com/sissilab/dp/ox1_creational/ox11_singleton/NewTest."<init>":()V
#4 = Class #21 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 LocalVariableTable
#10 = Utf8 this
#11 = Utf8 Lcom/sissilab/dp/ox1_creational/ox11_singleton/NewTest;
#12 = Utf8 main
#13 = Utf8 ([Ljava/lang/String;)V
#14 = Utf8 args
#15 = Utf8 [Ljava/lang/String;
#16 = Utf8 newTest
#17 = Utf8 SourceFile
#18 = Utf8 NewTest.java
#19 = NameAndType #5:#6 // "<init>":()V
#20 = Utf8 com/sissilab/dp/ox1_creational/ox11_singleton/NewTest
#21 = Utf8 java/lang/Object
{
public com.sissilab.dp.ox1_creational.ox11_singleton.NewTest(); // 自动生成的默认无参构造函数
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sissilab/dp/ox1_creational/ox11_singleton/NewTest;

public static void main(java.lang.String[]); // main 方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
// new:为由 #2 引用的类的对象分配内存 -> 将引用添加到操作数栈的顶部。
0: new #2 // class com/sissilab/dp/ox1_creational/ox11_singleton/NewTest
// dup:复制栈顶值 -> 将复制的引用添加到操作数栈的顶部(因为随后的 `invokespecial` 将消耗一个引用)。
3: dup
// invokespecial:调用构造函数以初始化新分配的空间并消耗一个引用。
4: invokespecial #3 // Method "<init>":()V
// astore_1:弹出栈顶值 -> 将引用分配给 LocalVariableTable 中位置 1 的元素(`newTest`)。
7: astore_1
8: return
LineNumberTable: // 将字节码指令映射到源文件中的行。
line 8: 0
line 9: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 newTest Lcom/sissilab/dp/ox1_creational/ox11_singleton/NewTest;
}
SourceFile: "NewTest.java"

虽然只有几行简单的源代码,但反编译后的内容却很庞大。主要集中在下面的代码部分:

1
2
3
4
0: new           #2                  // class com/sissilab/dp/ox1_creational/ox11_singleton/NewTest
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1

下面是该过程的说明:

HeapStack0: new #2The `new` instruction opens a space in the heap and a (ref) reference returned will be pushed onto the top of the stack.refHeapStackref3: dupThe `dup` instruction duplicates the top-of-stack referenceand pushes onto the top of the stackrefHeapStackref4: invokespecial #3The `invokespecial` instruction pops the top-of-stack reference and the just-allocated space will be initialized through invoking constructorHeapStackref7: astore_1The top-of-stack reference is popped and assigned to the element (`newTest`) at the position 1 of the LocalVariableTableLocalVariableTable0 args1 newTest

dup 指令需要复制栈顶元素的原因是因为下一步的 invokespecial 需要执行默认构造函数,这将从栈顶弹出一个引用。如果没有 dup 来复制栈顶元素,在执行 invokespecial 后将导致栈为空,从而导致新创建的对象丢失。

这解释了为什么 new 不是一个原子操作。尽管在 Java 代码中只有一行,但在字节码层面,它转化为四个关键指令操作。由于指令重排,new 中的三个步骤可以被重新排序。在多线程环境中,这可能导致上述单例模式的问题。因此,有必要在 instance 字段上添加 volatile 以防止重排。

1.5. 懒汉单例 - 双重检查锁定(DCL)

双重检查锁定是最推荐的懒汉初始化单例模式,确保了线程安全,并实现了延迟加载。完整代码如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 1.5. 懒汉单例 - 双重检查锁定(DCL)
*
* 优点:
* 1. 懒汉初始化
* 2. 可以通过双重检查锁定(DCL)确保线程安全
* 3. 可以减少锁定开销
* <p>
* 缺点:
* 1. 更复杂
* 2. 面对大量初始 `getInstance()` 请求时,可能会影响其性能
* <p>
* 最佳使用场景:
* 1. 当单例类持有大量资源时使用。
* 2. 当我们希望延迟创建单例实例直到真正需要时使用。
* 3. 当我们同时考虑性能和懒汉初始化时使用。
* 4. 当单例类属于轻量级或总是需被使用时,该场景可考虑可避免使用
*/
class LazySingletonDoubleCheck {

// 持有单例实例:添加 volatile 防止重排序
private static volatile LazySingletonDoubleCheck instance;

// 私有构造函数:限制用户自己创建实例。
private LazySingletonDoubleCheck() {
}

// 使用双重检查锁定
public static LazySingletonDoubleCheck getInstance() {
if (null == instance) {
synchronized (LazySingletonDoubleCheck.class) {
if (null == instance) {
instance = new LazySingletonDoubleCheck();
}
}
}
return instance;
}
}

1.5.1. 对“懒汉单例 - 双重检查锁定”的反射攻击

通常,为了避免反射攻击,我们通常在私有构造函数中添加检查,以查看 instance 字段是否为 null。如果 instance 不为 null,我们可以假设这个单例类已经创建了它的对象,并抛出异常以防止反射攻击。

然而,它容易受到反射攻击,这可能破坏预期的单例行为。我们来看以下两种情况:

  • 情况 1 (√):如果我们先调用 getInstance() 然后调用 constructor.newInstance(),将抛出错误以防止反射攻击。
  • 情况 2 (×):如果我们先调用 constructor.newInstance() 然后调用 getInstance(),我们将获得两个不同的实例,导致单例模式破裂。

因此,懒汉单例无法在所有情况下防止反射攻击。具体原因如下:

  1. 反射允许访问私有构造函数。攻击者可以使用反射机制绕过私有构造函数,创建单例类的新实例。
  2. 它缺乏任何固有机制来检查通过反射访问时单例类的实例是否已经存在。
  3. 在私有构造函数中抛出异常以防止反射攻击并不是一个万无一失的计划,因为攻击者仍然可以操控实例创建过程。
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
class LazySingletonDoubleCheckReflectionProof {

// 持有单例实例:添加 volatile 防止重排序
private static volatile LazySingletonDoubleCheckReflectionProof instance;

// 私有构造函数:限制用户自己创建实例,但不能防止构造函数反射
private LazySingletonDoubleCheckReflectionProof() {
// 不能防止反射攻击:第一次使用反射调用将仍然成功,但后续尝试将失败。
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 使用双重检查锁定
public static LazySingletonDoubleCheckReflectionProof getInstance() {
if (null == instance) {
synchronized (LazySingletonDoubleCheckReflectionProof.class) {
if (null == instance) {
instance = new LazySingletonDoubleCheckReflectionProof();
}
}
}
return instance;
}
}

1.5.2. 对“懒汉单例 - 双重检查锁定”的序列化攻击

序列化和反序列化一个类需要实现 java.io.Serializable,否则将抛出 java.io.NotSerializableException。序列化和反序列化的一般过程:

  1. 序列化单例类以生成序列化文件。
  2. 读取序列化文件通过反序列化获取单例类的实例。
  3. 比较序列化实例和反序列化实例是否相同。
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
26
// 该类必须实现 `java.io.Serializable` 以避免抛出 `java.io.NotSerializableException`。
class LazySingletonDoubleCheckSerialization implements Serializable {

// 持有单例实例:添加 volatile 防止重排序
private static volatile LazySingletonDoubleCheckSerialization instance;

// 私有构造函数:限制用户自己创建实例。
private LazySingletonDoubleCheckSerialization() {
// 不能在所有情况下防止反射攻击
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 使用双重检查锁定
public static LazySingletonDoubleCheckSerialization getInstance() {
if (null == instance) {
synchronized (LazySingletonDoubleCheckSerialization.class) {
if (null == instance) {
instance = new LazySingletonDoubleCheckSerialization();
}
}
}
return instance;
}
}

首先,我们将单例类序列化以生成序列化文件 -> 读取序列化文件并通过反序列化获取单例类的实例 -> 反序列化的实例与 getInstance() 的实例不相同,从而破坏单例模式。以下是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testSerializationAttackOnLazySingletonDoubleCheckSerialization() {
LazySingletonDoubleCheckSerialization instance = LazySingletonDoubleCheckSerialization.getInstance();

final Path serializedClassPath = Paths.get("TestSerialization-" + LazySingletonDoubleCheckSerialization.class.getSimpleName());

// 序列化:
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(serializedClassPath))) {
objectOutputStream.writeObject(instance);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 反序列化:构造函数将不会被调用,数据将通过从字节流读取来初始化。
LazySingletonDoubleCheckSerialization testSerializableInstance;

try (ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(serializedClassPath))) {
testSerializableInstance = ((LazySingletonDoubleCheckSerialization) objectInputStream.readObject());
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}

Assertions.assertSame(instance, testSerializableInstance); // ×
}

上述反序列化生成新对象的原因是序列化有其自己的机制。它读取字节流数据而不调用构造函数。对于非枚举单例,为防止序列化攻击,需要添加版本号,如 private static final long serialVersionUID = -1L;,并实现 readResolve() 方法,返回类的实例。这样就可以得到相同的对象。代码如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class LazySingletonDoubleCheckSerializationProof implements Serializable {

/**
* 主要用于版本控制,确保序列化和反序列化类版本之间的兼容性。
* 如果在序列化时未添加 `serialVersionUID`,将根据当前类数据自动生成一个版本号。
* 反序列化时,也会自动生成一个版本号。
* 如果两个版本号不匹配(例如,类内容在反序列化之前被修改),将报告以下错误:
* `java.io.InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -4190675925334731018, local class serialVersionUID = 1437610846000386433
* 指定 `serialVersionUID` 可以解决此问题。
*/
private static final long serialVersionUID = -1L;

// 持有单例实例:添加 volatile 防止重排序
private static volatile LazySingletonDoubleCheckSerializationProof instance;

// 私有构造函数:限制用户自己创建实例。
private LazySingletonDoubleCheckSerializationProof() {
// 不能在所有情况下防止反射攻击
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 使用双重检查锁定
public static LazySingletonDoubleCheckSerializationProof getInstance() {
if (null == instance) {
synchronized (LazySingletonDoubleCheckSerializationProof.class) {
if (null == instance) {
instance = new LazySingletonDoubleCheckSerializationProof();
}
}
}
return instance;
}

/**
* 对于非枚举单例模式,当面临序列化攻击时,必须实现 `readResolve()` 方法。
*
* @return
* @throws ObjectStreamException
*/
Object readResolve() throws ObjectStreamException {
// 反序列化实例是相同的,返回该实例
return getInstance();
}
}

我们来分析反序列化的源代码:objectInputStream.readObject() -> readObject(Object.class) -> Object obj = readObject0(type, false); -> 在 TC_OBJECT 的情况下:readOrdinaryObject(unshared)

readOrdinaryObject(unshared) 的关键点是:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private Object readOrdinaryObject(boolean unshared) throws IOException {
// ...

if (obj != null &&
handles.lookupException(passHandle) == null &&
// hasReadResolveMethod:检查当前类是否有 `readResolve()` 方法。
desc.hasReadResolveMethod())
{
// 调用当前类的 `readResolve()` 方法。
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// 过滤替换对象
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
// 通过 `readResolve()` 返回的 `rep` 被赋值给 `obj`。
handles.setObject(passHandle, obj = rep);
}
}

return obj;
}

boolean hasReadResolveMethod() {
requireInitialized();
// 如果 `readResolveMethod` 不为 null,表示当前类有 `readResolve()` 方法。
return (readResolveMethod != null);
}

private ObjectStreamClass(final Class<?> cl) {
// ...

// 获取当前类的 `readResolve()` 方法,并将其赋值给 `readResolveMethod` 变量
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

// ...
}

1.5.3. 完美版的“懒汉单例 - 双重检查锁定”

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 懒汉单例 - 双重检查锁定(DCL):完美版
* 1. 使用 volatile + DCL 来保证线程安全并减少同步的使用
* 2. 尝试使用私有构造函数检查来防止反射攻击,但不能在所有情况下保证
* 3. 使用定义的 serialVersionUID 和 readResolve() 来防止序列化攻击
* 4. 重写 clone() 方法以防止克隆攻击
* 5. 使用 final 类来防止子类化,避免任何子类破坏单例模式
*/
public final class LazySingletonDoubleCheckPerfect implements Serializable {

// 明确定义 `serialVersionUID` 可以避免潜在的兼容性问题。
private static final long serialVersionUID = -1L;

// 持有单例实例:添加 volatile 以防止重排序
private static volatile LazySingletonDoubleCheckPerfect instance;

// 私有构造函数:限制用户自行创建实例。
private LazySingletonDoubleCheckPerfect() {
// 无法在所有情况下防止反射攻击
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 使用双重检查锁定
public static LazySingletonDoubleCheckPerfect getInstance() {
if (null == instance) {
synchronized (LazySingletonDoubleCheckPerfect.class) {
if (null == instance) {
instance = new LazySingletonDoubleCheckPerfect();
}
}
}
return instance;
}

// 防止序列化攻击
private Object readResolve() throws ObjectStreamException {
// 在反序列化期间:返回现有实例,而不是创建新实例。
return getInstance();
}

// 防止克隆
@Override
protected Object clone() throws CloneNotSupportedException {
// (1) 抛出异常以防止克隆
throw new CloneNotSupportedException("Cloning of this singleton instance is not allowed");
// (2) 或者直接从克隆方法返回相同的实例
//return getInstance();
}
}

2. 饿汉式单例

饿汉式初始化模式在类加载时完成初始化,依赖于类加载机制来确保单例行为而无需特地人为加锁,从而实现更高的执行效率。然而,由于在类加载时进行实例化,它缺乏延迟加载的优势。如果实例未被使用,它将被不必要地实例化,导致资源浪费。

在以下两个饿汉式单例的例子中,它们都比懒汉式单例简单得多。这两种模式都利用了 JVM 的类加载机制来确保实例的唯一性。初始化只发生一次,并且 JVM 会同步类加载过程。

类加载过程包括:

  1. 加载:JVM 定位并将类文件的字节码加载到内存中,生成相应的类数据结构。
  2. 链接验证(验证加载的字节码的正确性)→ 准备(将静态字段初始化为默认值)→ 解析(将类文件中的符号引用解析为实际引用)
  3. 初始化:类中的静态初始化器静态代码块<clinit>中执行,此过程仅在类首次被使用时发生,例如创建实例或调用静态方法时。
  4. 使用:类被加载和初始化后,可以创建其实例,调用其方法以及访问其字段。
  5. 卸载:当类及其相关的类加载器不再使用且可以被垃圾回收时,类会从内存中移除。

由于 instance 变量是静态成员变量,其实例化发生在类加载的初始化阶段。此阶段涉及执行类构造器 <clinit>() 方法,编译器会自动收集类中的所有静态变量和静态代码块。因此,private static final EagerSingletonStaticConstant instance = new EagerSingletonStaticConstant(); static { instance = new EagerSingletonStaticBlock(); } 也在此方法中执行。JVM 确保类的 <clinit>() 方法在多线程环境中是同步的,从而保证了线程安全。

Tip

有些人可能会困惑为什么饿汉式单例更浪费资源。他们可能会想:“当那个饿汉式单例类从未使用时,似乎没有调用类的构造函数。”

实际上,这种情况更多地出现在调用类的其他方法时,而不是调用getInstance()方法。对于饿汉式单例,即使你不获取该单例的实例,仅仅是调用类的其他方法,也会由于类加载机制触发类的创建。但对于懒汉式单例,它不会触发类的创建,而只是执行该其他方法。

有关更多详细信息,请参阅此示例:EagerSingletonWastingReason.java at sissilab/DesignPattern-Lab (github.com)

2.1. 饿汉式单例 - 静态常量

这种实现非常简单,但可能会导致资源浪费,因为类的实例总是被创建,无论是否需要。而且在类创建期间处理异常也不容易。

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
26
27
28
29
30
31
32
/**
* 2.1. 饿汉式单例 - 静态常量
* <p>
* 优点:
* 1. 基于类加载机制保证线程安全,不需要同步操作
* 2. 实现简单易懂,无需额外的代码来处理懒加载或同步机制
* 3. 在 `getInstance()` 时没有性能开销,因为实例是在类加载时创建的
* <p>
* 缺点:
* 1. 可能会由于提前创建实例而浪费资源,尤其是对于拥有大缓存或数据库连接的重量级单例类
* 2. 缺乏懒加载
* 3. 使用静态常量时,处理类创建期间的异常不灵活
* <p>
* 最佳使用场景:
* 1. 当单例类相对轻量时使用。
* 2. 当单例类在应用程序生命周期中频繁使用时使用。
* 3. 当懒加载更合适时避免使用。
*/
class EagerSingletonStaticConstant {

// 持有单例实例:在类加载时初始化
private static final EagerSingletonStaticConstant instance = new EagerSingletonStaticConstant();

// 私有构造函数:禁止用户自行创建实例
private EagerSingletonStaticConstant() {
}

// 控制对单例实例的访问
public static EagerSingletonStaticConstant getInstance() {
return instance;
}
}

这段代码展示了饿汉式单例模式的简单实现,它依赖于静态常量来保证实例的唯一性和线程安全性。

2.1.1. 对“饿汉式单例 - 静态常量”的反射攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class EagerSingletonStaticConstantReflectionProof {

// 持有单例实例:在类加载时初始化
private static final EagerSingletonStaticConstantReflectionProof instance = new EagerSingletonStaticConstantReflectionProof();

// 私有构造函数:禁止用户自行创建实例
private EagerSingletonStaticConstantReflectionProof() {
// 防止反射攻击
if (instance != null) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static EagerSingletonStaticConstantReflectionProof getInstance() {
return instance;
}
}

2.1.2. 对“饿汉式单例 - 静态常量”的序列化攻击

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
26
27
class EagerSingletonStaticConstantSerializationProof implements Serializable {

// 显式定义 `serialVersionUID`,以避免潜在的不兼容问题
private static final long serialVersionUID = -1L;

// 持有单例实例:在类加载时初始化
private static final EagerSingletonStaticConstantSerializationProof instance = new EagerSingletonStaticConstantSerializationProof();

// 私有构造函数:禁止用户自行创建实例
private EagerSingletonStaticConstantSerializationProof() {
// 防止反射攻击
if (instance != null) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static EagerSingletonStaticConstantSerializationProof getInstance() {
return instance;
}

// 防止反序列化时创建新实例
Object readResolve() throws ObjectStreamException {
// 在反序列化过程中:返回现有的实例,而不是创建新实例
return getInstance();
}
}

2.1.3. 完美版的“饿汉式单例 - 静态常量”

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 饿汉式单例 - 静态常量:完美版本
* 1. 使用静态常量基于类加载机制创建单例实例
* 2. 尝试使用私有构造函数检查来防止反射攻击,但无法在所有情况下保证
* 3. 使用定义的 serialVersionUID 和 readResolve() 来防止序列化攻击
* 4. 重写 clone() 以防止克隆攻击
* 5. 使用 final 类以防止子类化,避免任何子类破坏单例模式
*/
public final class EagerSingletonStaticConstantPerfect implements Serializable {

// 显式定义 `serialVersionUID` 以避免潜在的不兼容问题
private static final long serialVersionUID = -1L;

// 持有单例实例:在类加载时初始化
private static final EagerSingletonStaticConstantPerfect instance = new EagerSingletonStaticConstantPerfect();

// 私有构造函数:禁止用户自行创建实例
private EagerSingletonStaticConstantPerfect() {
// 无法在所有情况下防止反射攻击
if (instance != null) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static EagerSingletonStaticConstantPerfect getInstance() {
return instance;
}

// 防止序列化攻击
private Object readResolve() throws ObjectStreamException {
// 在反序列化过程中:返回现有的实例,而不是创建新实例
return getInstance();
}

// 防止克隆
@Override
protected Object clone() throws CloneNotSupportedException {
// (1) 抛出异常以防止克隆
throw new CloneNotSupportedException("Cloning of this singleton instance is not allowed");
// (2) 或者直接从clone方法返回相同的实例
//return getInstance();
}
}

2.2. 饿汉式单例 - 静态块

使用静态块完成类实例化也可能由于类加载机制导致资源浪费。但在静态块中处理异常和执行更多操作会更容易。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 2.2. 饿汉式单例 - 静态块
* <p>
* 优点:
* 1. 可以基于类加载机制保证创建是线程安全的,无需同步
* 2. 更直接且易于理解,无需额外代码来处理延迟初始化或同步机制
* 3. 在 `getInstance()` 时没有性能开销,因为实例是在类加载时创建的
* 4. 在创建过程中,静态块灵活地处理更复杂的逻辑或异常
* <p>
* 缺点:
* 1. 由于过早创建可能浪费资源,尤其是对于资源密集型的单例类,如大型缓存或数据库连接
* 2. 缺乏延迟初始化
* <p>
* 最佳使用场景:
* 1. 在单例类相对轻量时使用。
* 2. 在应用程序生命周期中频繁需要单例类时使用。
* 3. 在需要复杂初始化步骤时使用,因为静态块提供了灵活性来处理。
* 4. 当延迟加载更可取时避免使用。
*/
class EagerSingletonStaticBlock {

// 持有单例实例:在类加载时初始化
private static final EagerSingletonStaticBlock instance;

// 静态块中创建该单例类的实例
static {
// ...
instance = new EagerSingletonStaticBlock();
// ...
}

// 私有构造函数:禁止用户自行创建实例
private EagerSingletonStaticBlock() {
}

// 控制对单例实例的访问
public static EagerSingletonStaticBlock getInstance() {
return instance;
}
}

2.2.1. 对“饿汉式单例 - 静态块”的反射攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class EagerSingletonStaticBlockReflectionProof {

// 持有单例实例:在静态块中加载类时初始化
private static final EagerSingletonStaticBlockReflectionProof instance;

static {
instance = new EagerSingletonStaticBlockReflectionProof();
}

// 私有构造函数:限制用户自行创建实例
private EagerSingletonStaticBlockReflectionProof() {
// 防止反射攻击
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static EagerSingletonStaticBlockReflectionProof getInstance() {
return instance;
}
}

2.2.2. 对“饿汉式单例 - 静态块”的序列化攻击

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
26
27
28
29
30
31
class EagerSingletonStaticBlockSerializationProof implements Serializable {

// 明确定义 `serialVersionUID` 可以避免潜在的不兼容问题。
private static final long serialVersionUID = -1L;

// 持有单例实例:在静态块中加载类时初始化
private static final EagerSingletonStaticBlockSerializationProof instance;

static {
instance = new EagerSingletonStaticBlockSerializationProof();
}

// 私有构造函数:限制用户自行创建实例
private EagerSingletonStaticBlockSerializationProof() {
// 防止反射攻击
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static EagerSingletonStaticBlockSerializationProof getInstance() {
return instance;
}

// 防止序列化攻击
Object readResolve() throws ObjectStreamException {
// 在反序列化期间:返回现有实例,而不是创建一个新的实例。
return getInstance();
}
}

2.2.3. 完美版本的“饿汉式单例 - 静态块”

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 饿汉式单例 - 静态块:完美版本
* 1. 使用静态块基于类加载机制创建单例实例
* 2. 尝试使用私有构造函数检查以防止反射攻击,但在所有情况下无法保证
* 3. 使用定义的 serialVersionUID 和 readResolve() 来防止序列化攻击
* 4. 重写 clone() 方法以防止克隆攻击
* 5. 使用 final 类防止子类化,避免任何子类破坏单例模式
*/
public final class EagerSingletonStaticBlockPerfect implements Serializable {

// 明确定义 `serialVersionUID` 可以避免潜在的不兼容问题。
private static final long serialVersionUID = -1L;

// 持有单例实例:在加载类时初始化
private static final EagerSingletonStaticBlockPerfect instance;

static {
// ...
instance = new EagerSingletonStaticBlockPerfect();
// ...
}

// 私有构造函数:限制用户自行创建实例。
private EagerSingletonStaticBlockPerfect() {
// 在所有情况下无法防止反射攻击
if (null != instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static EagerSingletonStaticBlockPerfect getInstance() {
return instance;
}

// 防止序列化攻击
private Object readResolve() throws ObjectStreamException {
// 在反序列化期间:返回现有实例,而不是创建一个新的实例。
return getInstance();
}

// 防止克隆
@Override
protected Object clone() throws CloneNotSupportedException {
// (1) 抛出异常以防止克隆
throw new CloneNotSupportedException("Cloning of this singleton instance is not allowed");
// (2) 或直接从克隆方法返回相同实例
//return getInstance();
}
}

3. 静态内部类单例

静态内部类单例模式本质上也利用了类加载机制来确保线程安全。由于其特性,它可以保证类初始化仅在实际使用时触发,这也是一种懒加载的形式。

  • 1.5. 懒汉单例 - 双重检查锁定(DCL) 相比,这种模式也能实现类似的懒加载效果,但实现更简单。
  • 2. 饿汉式单例 相比,这种模式同样利用类加载机制来初始化并确保只有一个类实例。当类被加载时,饿汉式单例将被实例化,但静态内部类单例可能不会。这是因为实例只有在其静态内部类 InstanceHolder 被使用和加载时才会创建。因此,StaticInnerClassSingleton 的实例不会在方法 getInstance() 被调用之前创建,这样就实现了懒加载的效果。
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
26
27
28
29
30
31
32
33
34
35
/**
* 3. 静态内部类单例
* <p>
* 优点:
* 1. 基于静态内部类特性实现懒加载
* 2. 可以保证 `InstanceHolder` 类的静态初始化在类加载机制下的线程安全
* 3. 避免了与 DCL 模式相关的同步开销
* 4. 相比于 DCL 模式,相对更容易理解
* <p>
* 缺点:
* 1. 对于类创建过程中出现的异常处理不够灵活
* 2. 由于类加载时机可能导致意外问题
* <p>
* 最佳使用场景:
* 1. 当单例类持有大量资源时使用
* 2. 当希望延迟单例实例的创建直到真正需要时使用
* 3. 当需要复杂初始化步骤时避免使用
*/
public class StaticInnerClassSingleton {

// 静态内部类提供类的单例实例
private static class InstanceHolder {
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}

// 私有构造函数:限制用户自行创建实例。
private StaticInnerClassSingleton() {
}

// 控制对单例实例的访问
public static StaticInnerClassSingleton getInstance() {
// 只有在这里才会触发 `InstanceHolder.instance` 的初始化
return InstanceHolder.instance;
}
}

3.1. 对“静态内部类单例”的反射攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class StaticInnerSingletonReflectionProof {

// 静态内部类提供类的单例实例
private static class InstanceHolder {
private static StaticInnerSingletonReflectionProof instance = new StaticInnerSingletonReflectionProof();
}

// 私有构造函数:限制用户自行创建实例,但无法防止构造函数的反射
private StaticInnerSingletonReflectionProof() {
// 防止反射攻击
if (null != InstanceHolder.instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static StaticInnerSingletonReflectionProof getInstance() {
// 只有在这里才会触发 `InstanceHolder.instance` 的初始化。
return InstanceHolder.instance;
}
}

3.2. 对“静态内部类单例”的序列化攻击

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class StaticInnerSingletonSerializationProof implements Serializable {

/**
* 如果在序列化过程中没有添加 `serialVersionUID`,版本号将根据当前类数据自动生成。
* 在反序列化时,也会自动生成一个版本号。
* 如果两个版本号不匹配(类内容在反序列化前被修改),则会报告以下错误:
* `java.io.InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -6193354024038071874, local class serialVersionUID = 3430229600994946531`
* 指定一个 serialVersionUID 可以解决此问题。
*/
private static final long serialVersionUID = -1L;

// 静态内部类提供类的单例实例
private static class InstanceHolder {
private static StaticInnerSingletonSerializationProof instance = new StaticInnerSingletonSerializationProof();
}

// 私有构造函数:限制用户自行创建实例,但无法防止构造函数的反射
private StaticInnerSingletonSerializationProof() {
// 防止反射攻击
if (null != InstanceHolder.instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static StaticInnerSingletonSerializationProof getInstance() {
return InstanceHolder.instance;
}

/**
* 对于非枚举单例模式,在面对序列化攻击时,必须实现 `readResolve()` 方法。
*
* @return
* @throws ObjectStreamException
*/
Object readResolve() throws ObjectStreamException {
// 在反序列化期间:返回现有实例,而不是创建一个新的实例。
return getInstance();
}
}

3.3. 完美版本的“静态内部类单例”

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 静态内部类单例:完美版本
* 1. 利用静态内部类的特性提供懒加载和基于类加载机制的线程安全
* 2. 尝试使用私有构造函数检查以防止反射攻击,但在所有情况下无法保证
* 3. 使用定义的 serialVersionUID 和 readResolve() 来防止序列化攻击
* 4. 重写 clone() 方法以防止克隆攻击
* 5. 使用 final 类防止子类化,避免任何子类破坏单例模式
*/
public final class StaticInnerSingletonPerfect implements Serializable {

// 明确定义 `serialVersionUID` 可以避免潜在的不兼容问题。
private static final long serialVersionUID = -1L;

// 静态内部类提供类的单例实例
private static class InstanceHolder {
private static StaticInnerSingletonPerfect instance = new StaticInnerSingletonPerfect();
}

// 私有构造函数:限制用户自行创建实例
private StaticInnerSingletonPerfect() {
// 防止反射攻击
if (null != InstanceHolder.instance) {
throw new IllegalStateException("This singleton instance already exists.");
}
}

// 控制对单例实例的访问
public static StaticInnerSingletonPerfect getInstance() {
return StaticInnerSingletonPerfect.InstanceHolder.instance;
}

// 防止序列化攻击
Object readResolve() throws ObjectStreamException {
// 在反序列化期间:返回现有实例,而不是创建一个新的实例。
return getInstance();
}

// 防止克隆
@Override
protected Object clone() throws CloneNotSupportedException {
// (1) 抛出异常以防止克隆
throw new CloneNotSupportedException("Cloning of this singleton instance is not allowed");
// (2) 或直接从克隆方法返回相同实例
//return getInstance();
}
}

4. 枚举型单例

枚举型单例被认为是实现单例模式的最佳方式。它不仅利用了类加载机制来保证线程安全,还防止在反序列化过程中重建对象,并避免了反射攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 4. 枚举型单例
* <p>
* 优点:
* 1. 实现非常简单,代码简洁。
* 2. 天然线程安全,因为使用了类加载机制、私有构造函数和公共静态最终实例字段。
* 3. 可以防止反射攻击,因为调用 `newInstance()` 时会抛出 IllegalArgumentException。
* 4. 自带序列化功能,可以防止序列化攻击。
* <p>
* 缺点:
* 1. 没有懒加载。
* 2. 不能扩展其他类,因为枚举类隐式继承 `java.lang.Enum`。
* 3. 不方便调试,因为一些关键代码是隐式自动生成的,比如私有构造函数、静态初始化块等。
*/
public enum EnumSingleton {
instance;

public void anyMethod() {
}
}

让我们分析一下枚举类的字节码。在编译 EnumSingleton.java 并通过 javac -g EnumSingleton.java 生成类文件后,执行 javap -v -p EnumSingleton.class 以生成字节码文件,如下所示:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
Classfile /DesignPattern-Lab/src/main/java/com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton.class
Last modified 26/08/2024; size 1195 bytes
MD5 checksum 1e21098493b1502d48f3a4fba5884dcc
Compiled from "EnumSingleton.java"
// 枚举类隐式继承 `java.lang.Enum`
public final class com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton extends java.lang.Enum<com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#34 // com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton.$VALUES:[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#2 = Methodref #35.#36 // "[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;".clone:()Ljava/lang/Object;
#3 = Class #14 // "[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;"
#4 = Class #37 // com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton
#5 = Methodref #10.#38 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #10.#39 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = String #11 // instance
#8 = Methodref #4.#39 // com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton."<init>":(Ljava/lang/String;I)V
#9 = Fieldref #4.#40 // com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton.instance:Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#10 = Class #41 // java/lang/Enum
#11 = Utf8 instance
#12 = Utf8 Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#13 = Utf8 $VALUES
#14 = Utf8 [Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#15 = Utf8 values
#16 = Utf8 ()[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 valueOf
#20 = Utf8 (Ljava/lang/String;)Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#21 = Utf8 LocalVariableTable
#22 = Utf8 name
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 <init>
#25 = Utf8 (Ljava/lang/String;I)V
#26 = Utf8 this
#27 = Utf8 Signature
#28 = Utf8 ()V
#29 = Utf8 anyMethod
#30 = Utf8 <clinit>
#31 = Utf8 Ljava/lang/Enum<Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;>;
#32 = Utf8 SourceFile
#33 = Utf8 EnumSingleton.java
#34 = NameAndType #13:#14 // $VALUES:[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#35 = Class #14 // "[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;"
#36 = NameAndType #42:#43 // clone:()Ljava/lang/Object;
#37 = Utf8 com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton
#38 = NameAndType #19:#44 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#39 = NameAndType #24:#25 // "<init>":(Ljava/lang/String;I)V
#40 = NameAndType #11:#12 // instance:Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
#41 = Utf8 java/lang/Enum
#42 = Utf8 clone
#43 = Utf8 ()Ljava/lang/Object;
#44 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
// `instance` 是一个 `public static final` 字段
public static final com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton instance;
descriptor: Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

// `private static final $VALUES` 字段保存所有枚举常量
private static final com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton[] $VALUES;
descriptor: [Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

// `values()` 方法是由编译器自动生成的,用于返回定义的枚举常量数组
public static com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton[] values();
descriptor: ()[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
3: invokevirtual #2 // Method "[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;"
9: areturn
LineNumberTable:
line 17: 0

public static com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton
9: areturn
LineNumberTable:
line 17: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;

// 自动生成的两个参数(String, int)的私有构造函数
private com.sissilab.dp.ox1_creational.ox11_singleton.EnumSingleton();
descriptor: (Ljava/lang/String;I)V
flags: ACC_PRIVATE
Code:
stack=3, locals=3, args_size=3
// 将 `this` 引用加载到栈上(来自 slot 0)
0: aload_0
// 将存储在局部变量 1 中的第一个参数(枚举常量的 `name`)加载到栈上
1: aload_1
// 将存储在局部变量 2 中的第二个参数(枚举常量的 `ordinal`)加载到栈上
2: iload_2
// 调用超类构造函数 `java.lang.Enum (String name, int ordinal)`
3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
LineNumberTable:
line 17: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
Signature: #28 // ()V

public void anyMethod();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 21: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;

// 静态初始化块
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
// 为由 #4 引用的类分配内存 -> 将引用添加到操作数栈的顶部
0: new #4 // class com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton
// dup:复制栈顶值 -> 将复制的引用添加到操作数栈的顶部
3: dup
// ldc:将字符串实例加载到栈上(`String name`)
4: ldc #7 // String instance
// iconst_0:将整数常量 0 压入栈中(`int ordinal`)
6: iconst_0
// invokespecial:调用构造函数 EnumSingleton(String, int) 进行初始化
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
// putstatic:将创建的实例分配给 EnumSingleton 的静态字段 `instance`
10: putstatic #9 // Field instance:Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
// iconst_1:将整数 1(要创建的数组的大小)压入栈中
13: iconst_1
// anewarray:创建一个新的 EnumSingleton 对象数组(大小为 1)
14: anewarray #4 // class com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton
// dup:复制栈上的数组引用
17: dup
// iconst_0:将整数 0(数组的索引)压入栈中
18: iconst_0
// getstatic:获取静态字段 `instance`
19: getstatic #9 // Field instance:Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
// aastore:将实例存储到数组的索引 0 处
22: aastore
// putstatic:将数组存储到静态字段 `$VALUES` 中(它按顺序保存所有枚举常量)
23: putstatic #1 // Field $VALUES:[Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;
26: return
LineNumberTable:
line 18: 0
line 17: 13
}
Signature: #31 // Ljava/lang/Enum<Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;>;
SourceFile: "EnumSingleton.java"

实际上,初始化过程发生在静态初始化块(static {},它会在类首次加载时执行),关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 为由 #4 引用的类分配内存 -> 将引用添加到操作数栈的顶部
0: new #4 // class com/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton

// dup:复制栈顶值 -> 将复制的引用添加到操作数栈的顶部
3: dup

// ldc:将字符串实例加载到栈上(`String name`)
4: ldc #7 // String instance

// iconst_0:将整数常量 0 压入栈中(`int ordinal`)
6: iconst_0

// invokespecial:调用构造函数 EnumSingleton(String, int) 进行初始化
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V

// putstatic:将创建的实例分配给 EnumSingleton 的静态字段 `instance`
10: putstatic #9 // Field instance:Lcom/sissilab/dp/ox1_creational/ox11_singleton/EnumSingleton;

4.1. 对“枚举型单例”的反射攻击

当尝试通过反射获取枚举类的实例时,会抛出错误:java.lang.IllegalArgumentException: Cannot reflectively create enum objects。这有效地防止了反射攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testReflectionAttackOnEnumSingleton() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取构造函数:只会自动生成一个带有两个参数的私有构造函数,如 `private EnumSingleton(String, int)`
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
// 设置构造函数的可访问性
constructor.setAccessible(true);
// reflectInstance: 通过构造函数创建实例
EnumSingleton reflectInstance = constructor.newInstance("instance", 0); // java.lang.IllegalArgumentException: Cannot reflectively create enum objects

// instance: 通过正常的 public static final `instance` 字段获取实例
EnumSingleton instance = EnumSingleton.instance;

Assertions.assertSame(reflectInstance, instance); // ×
}

实际上,错误是在 public T newInstance(Object ... initargs) 中抛出的。以下源代码表明有一个显式检查当前类是否为枚举类型,如果是,则会抛出 IllegalArgumentException。因此,枚举单例可以有效防止反射攻击。

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 final class Constructor<T> extends Executable {
// ...

@CallerSensitive
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 如果当前类是枚举类型,则会抛出 `IllegalArgumentException`。
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // 读取 volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

// ...
}

4.2. 对“枚举型单例”的序列化攻击

每个枚举类隐式继承自 java.lang.Enum 抽象类,该类实现了 java.io.Serializable,这意味着枚举类可以支持序列化和反序列化。得益于其内置机制,枚举单例天然能够抵御序列化攻击,因为在反序列化过程中不会创建新的枚举常量实例。

让我们看看在反序列化源代码中枚举单例发生了什么。在 readObject0(type, false) 方法中,针对 TC_ENUM 的情况:–> readEnum(boolean unshared)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private Enum<?> readEnum(boolean unshared) throws IOException {
// 仅处理作为枚举序列化的对象
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
// 如果该类不是枚举,则会抛出 InvalidClassException。
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}

// 获取枚举常量的名称,例如 `instance`(每个常量名称在枚举中必须是唯一的)。
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
// 使用枚举类(例如 `EnumSingleton`)和常量名称(例如 `instance`)查找常量
// 这是关键部分,无论枚举序列化和反序列化多少次,都不会创建新的枚举实例。
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
// 在默认模式(共享模式)下,`unshared` 为 false。这意味着如果对象在序列化流中出现多次,所有指向该对象的引用都将指向同一个反序列化实例。
// 将更新现有的句柄(`Object[] entries`),确保重用同一枚举实例,尤其是对于枚举单例。
handles.setObject(enumHandle, result);
}
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
// 从内部映射 (`Map<String, T> enumConstantDirectory`) 中通过名称获取枚举常量
// 在 `enumConstantDirectory()` 内部有一个预构建的映射,包含所有加载到内存中的枚举常量,确保每个枚举常量的唯一性。
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}

4.3. 完美版本的“枚举型单例”

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 使用此单例:
* `EnumSingletonPerfect.instance.anyMethod();`
*/
public enum EnumSingletonPerfect {
instance;

public void anyMethod() {
}

/**
* 访问延迟加载的资源:
* `EnumSingletonPerfect.instance.getExpensiveResource().useResource();`
*/
// 使用静态内部类来实现重资源的延迟加载
private static class LazyHeavyResource {
private static final HeavyResource RESOURCE = new HeavyResource();
}

public HeavyResource getExpensiveResource() {
return LazyHeavyResource.RESOURCE;
}

// 这个静态内部类代表一个重资源。
private static class HeavyResource {
HeavyResource() {
// 模拟耗费资源的初始化场景……需要大量时间来加载
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

void useResource() {
System.out.println("Use this heavy resource...");
}
}
}

5. 在源代码中的应用

5.1. java.lang.Runtime (饿汉式单例 - 静态常量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 饿汉式单例 - 静态常量:单例实例 `currentRuntime` 在类加载时初始化,确保实例提前创建并且是线程安全的。
public class Runtime {
// 持有单例实例:在加载类时初始化
private static Runtime currentRuntime = new Runtime();

// 私有构造函数:防止外部实例化
private Runtime() {}

// 控制访问单例实例
public static Runtime getRuntime() {
return currentRuntime;
}

// ...
}

5.2. org.quartz.impl.SchedulerRepository (懒汉式单例 - synchronized)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Scheduler scheduler = new StdSchedulerFactory().getScheduler(); -> SchedulerRepository schedRep = SchedulerRepository.getInstance();
public class SchedulerRepository {
// 持有单例实例
private static SchedulerRepository inst;

// 私有构造函数:防止外部实例化
private SchedulerRepository() {}

// 控制访问单例实例:使用静态同步方法以确保线程安全
public static synchronized SchedulerRepository getInstance() {
if (inst == null) {
inst = new SchedulerRepository();
}
return inst;
}
// ...
}

5.3. Spring - org.springframework.beans.factory.support.DefaultSingletonBeanRegistry (懒汉式单例 - 双重检查锁定 DCL)

Spring 的单例模式:当在 Spring 应用程序上下文中定义一个 bean 时,如果没有明确指定作用域,Spring 会将其视为单例。这意味着 Spring 只会创建一个实例,该实例可以被所有类共享。通过在 Spring 中利用这种单例模式,我们可以享受到高效的内存管理、集中化的 bean 配置,以及在整个应用程序中共享和重用 bean 实例的好处。

在 Spring 中,加载单例的过程通常从 org.springframework.beans.factory.BeanFactorygetBean() 方法开始,其默认实现是抽象类 org.springframework.beans.factory.support.AbstractBeanFactory。各种 getBean() 方法最终调用 AbstractBeanFactorydoGetBean() 方法,例如 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// org.springframework.beans.factory.support.AbstractBeanFactory
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
// ...

protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
// 获取转换后的 bean 名称,以防名称包含非法字符
final String beanName = transformedBeanName(name);
Object bean;

// 检查单例缓存以查找手动注册的单例
// 通常情况下,它将在第一次请求 bean 时返回 null。
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ...

// 获取给定 bean 实例的对象,要么是 bean 实例本身,要么是 FactoryBean 创建的对象。
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// ...
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// ...

// 创建 bean 实例
if (mbd.isSingleton()) {
// 若该 bean 是单例,它将会进入重载的 `getSingleton()` 方法
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// ...
} else {
// ...
}
} catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// ...
return (T) bean;
}

// ...
}

该代码 Object sharedInstance = getSingleton(beanName);getBean() 方法中是 Spring 中单例模式(双重检查)的核心实现。getSingleton() 方法在 AbstractBeanFactory 的父类 DefaultSingletonBeanRegistry 中定义。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// ...

// 单例对象缓存(完全初始化的单例 bean):bean 名称 -> bean 实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 早期单例对象缓存(当前正在创建的单例 bean,部分创建):bean 名称 -> bean 实例
// 这是解决循环依赖的关键。
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 单例工厂缓存:bean 名称 -> ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

@Override
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}

// 根据 DCL 模式从缓存中获取 bean
// `singletonObjects` -> `earlySingletonObjects` -> `singletonFactories` -> 通过 `singletonFactory.getObject()` 创建单例 bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// (1) `singletonObjects`:检查缓存中是否包含 bean
Object singletonObject = this.singletonObjects.get(beanName);
// 在 `singletonObjects` 中找不到单例。&& 单例 bean 当前正在创建中。
// isSingletonCurrentlyInCreation(beanName):如果为真,则单例 bean 正在创建过程中但尚未完全初始化。
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) { // 在 `singletonObjects` 映射上同步以确保线程安全
// (2) `earlySingletonObjects`:此缓存保存正在创建的 bean。
singletonObject = this.earlySingletonObjects.get(beanName);
// 在 `earlySingletonObjects` 中找不到单例。&& `allowEarlyReference` 为真。
if (singletonObject == null && allowEarlyReference) {
// 当某些方法需要提前初始化时,调用 `addSingleFactory()` 方法将相应的 `ObjectFactory` 初始化策略存储在 `singletonFactories` 映射中。
// (3) `singletonFactories`:通过 beanName 获取 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 创建并获取 bean
singletonObject = singletonFactory.getObject();
// 更新两个缓存:`earlySingletonObjects` 和 `singletonFactories` 互斥。
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

// ...
}

一般来说,当第一次在 doGetBean() 中调用 getSingleton() 时,缓存中找不到对应的 bean 实例。然后,它进入 if (mbd.isSingleton()) { } 块,调用重载的 getSingleton() 方法。在此方法中,bean 被实例化,然后实例被缓存到 singletonObjects 映射中,从而允许在后续请求中直接返回单例 bean。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// ...

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
// 由于创建过程涉及对 `singletonObjects` 的操作,因此需要加锁。
synchronized (this.singletonObjects) {
// 1. 再次尝试从 `singletonObjects` 映射中获取 bean,并检查 bean 是否已加载。如果 bean 已加载,则直接返回。
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 2. 如果当前 BeanFactory 正在被销毁,则直接抛出异常,不允许创建单例 bean。
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 3. 创建 bean 之前的一些准备工作:
// beanName 被记录为当前正在加载(添加到 `singletonsCurrentlyInCreation` 缓存中)。
// 如果 bean 已经在加载过程中,则抛出异常。
// 这是为了解决循环引用问题。
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<Exception>();
}
try {
// 4. 通过回调机制获取 bean 实例。
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException ex) {
// 单例对象在此期间是否隐式出现 ->
// 如果是,则继续使用它,因为该异常表明了状态。
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
} catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 5. 单例 bean 加载后的后处理步骤:
// 移除当前正在创建的 bean 的记录(从 `singletonsCurrentlyInCreation` 缓存中移除 beanName)。
afterSingletonCreation(beanName); // 从 singletonsCurrentlyInCreation 中移除该 beanName
}
if (newSingleton) {
// 6. 添加到 `singletonObjects` 缓存并从其他辅助缓存中移除(例如 `singletonFactories`、`earlySingletonObjects`)
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}

// ...
}

5.4. Spring - org.springframework.core.ReactiveAdapterRegistry (懒汉式单例 - 双重检查锁定 DCL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ReactiveAdapterRegistry {
// 持有单例实例:添加 volatile 以防止重排序
@Nullable
private static volatile ReactiveAdapterRegistry sharedInstance;

// 使用双重检查锁定
public static ReactiveAdapterRegistry getSharedInstance() {
ReactiveAdapterRegistry registry = sharedInstance;
if (registry == null) {
synchronized (ReactiveAdapterRegistry.class) {
registry = sharedInstance;
if (registry == null) {
registry = new ReactiveAdapterRegistry();
sharedInstance = registry;
}
}
}
return registry;
}

// ...
}

5.5. Spring - org.springframework.aop.framework.ProxyFactoryBean (懒汉式单例 - synchronized)

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
26
27
public class ProxyFactoryBean extends ProxyCreatorSupport
implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {

private Object singletonInstance;

// 控制对单例实例的访问:使用同步方法确保线程安全。
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// 依赖 AOP 机制来告诉我们要代理哪些接口
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// 初始化共享的单例实例。
super.setFrozen(this.freezeProxy);
// 创建 AOP 动态代理 和 获取实例
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}

// ...
}

5.6. Tomcat - org.apache.catalina.webresources.TomcatURLStreamHandlerFactory (懒汉式单例 - 双重检查锁定 DCL)

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
26
public class TomcatURLStreamHandlerFactory implements URLStreamHandlerFactory {

// 持有单例实例:添加 volatile 来防止指定重排
private static volatile TomcatURLStreamHandlerFactory instance = null;

public static TomcatURLStreamHandlerFactory getInstance() {
getInstanceInternal(true);
return instance;
}

// 使用 双重检查锁定 DCL
private static TomcatURLStreamHandlerFactory getInstanceInternal(boolean register) {
if (instance == null) {
Class var1 = TomcatURLStreamHandlerFactory.class;
synchronized(TomcatURLStreamHandlerFactory.class) {
if (instance == null) {
instance = new TomcatURLStreamHandlerFactory(register);
}
}
}

return instance;
}

// ...
}

References