单例模式

简述

单例 (Singleton) 模式提供一个可以全局访问的实例,并保证该类仅有一个实例。[1]

设计模式类型:创建型[2]

实现

1.懒汉 多线程不安全

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

private Singleton() {
}

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

2.懒汉 多线程安全

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

private Singleton() {
}

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

3.双检锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
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;
}
}

4.饿汉

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

private Singleton() {
}

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

5.静态内部类

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

private Singleton() {
}

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

6.枚举

1
2
3
public enum Singleton {
INSTANCE;
}

选择

不建议使用 1 和 2[3]

不考虑继承问题使用 6 枚举[4]

单例是派生类,确定会使用,建议使用 4 饿汉[5]

单例是派生类,且不确定单例是否会使用,考虑 3 双检锁 或 5 静态内部类[6]


  1. GoF Design Patterns 把 Singleton 的意图概括为保证一个类只有一个实例,并提供访问该实例的全局访问点;《Effective Java》第 3 条也把 Java Singleton 放在“实例控制”的语境下讨论:https://www.informit.com/store/design-patterns-elements-of-reusable-object-oriented-9780201633610https://www.informit.com/store/effective-java-9780134686042↩︎

  2. GoF Design Patterns 将 Singleton 归入 Creational Patterns,因为它关注对象创建与实例控制,而不是对象结构或对象间行为协作:https://www.informit.com/store/design-patterns-elements-of-reusable-object-oriented-9780201633610↩︎

  3. 未同步的懒汉式在多线程下可能让多个线程同时观察到空引用并各自创建实例;synchronized getInstance() 能串行化创建过程,但每次访问都进入同步路径。这里的“不建议”主要对应并发安全和热路径同步成本两个边界,依据 Java 同步语义、JLS 内存模型和 Singleton 创建路径推导:https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html#jls-17.4↩︎

  4. 《Effective Java》第 3 条推荐单元素枚举作为 Java Singleton 的简洁写法;JLS 规定 enum 类实例由枚举常量定义,反射 API 也禁止通过 Constructor.newInstance 创建 enum 实例,序列化规范对 enum 常量有专门规则:https://www.informit.com/store/effective-java-9780134686042https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.9https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Constructor.html#newInstance(java.lang.Object...)https://docs.oracle.com/en/java/javase/21/docs/specs/serialization/serial-arch.html#serialization-of-enum-constants↩︎

  5. 饿汉式依赖类初始化期间创建 static final 实例;JLS 的类初始化规则和 static 字段规则说明静态字段属于类变量,并在类初始化过程中完成初始化,因此它适合“确定会使用”的简单单例,但也会把创建时机绑定到类初始化:https://docs.oracle.com/javase/specs/jls/se21/html/jls-12.html#jls-12.4https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3.1.1↩︎

  6. 双重检查锁在 Java 中必须配合 volatile 字段,依赖可见性和 happens-before 语义避免发布不完整对象;静态内部类 holder 则利用类初始化的延迟触发,首次主动访问 holder 的静态字段时才创建实例。需要普通类形式的延迟单例时,静态 holder 通常比双重检查锁更少手写并发协议:https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3.1.4https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html#jls-17.4https://docs.oracle.com/javase/specs/jls/se21/html/jls-12.html#jls-12.4↩︎