单例模式所要实现的目标:保持一个类有且只有一个实例。
出于性能的考虑,不少单例模式都会采用延迟加载的方式。根据是否延迟创建对象,可以分为:
😪 懒汉式
单线程的单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public final class Singleton {
private static Singleton instance = null;
// 构造器私有,其他类无法通过 new 初始化
private Singleton() { }
// 创建并返回该类的唯一实例
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
}
public void someService() {
// 一些业务代码
}
}
|
🔒 加锁的单例模式
使用 synchronized
加锁实现线程安全。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public final class Singleton {
private static Singleton instance = null;
private Singleton() { }
// public static synchronized getInstance()
public static Singleton getInstance() {
synchronized (Singleton.class) { // 不推荐 🙅♂️ 每次判空操作,都要获取锁,开销极大
if (null == instance) {
instance = new Singleton();
}
}
return instance;
}
}
|
使用双重检验判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 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
修饰实例,利用了以下两个作用:
(1) 保障可见性:
一个线程初始化了 instance
的值,其他线程可以读取到相应的值。
(2) 保障有序性:
保障一个线程读取到的 instance
引用的实例已经初始化完毕。
对象实例化会分解成以下几个子操作:
- 分配对象所需的空间
- 初始化 instance 引用的对象
- 将对象的引用写入共享变量
由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2
。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。
例如:线程 T1 执行了 1 和 3 后,此时 T2 调用 getInstance()
后发现 instance
不为空,因此直接返回,但此时 instance
还未被初始化。
静态内部类的单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public final class Singleton {
private Singleton() { }
private static class InstanceHolder { // 静态内部类
// 保存外部类的唯一实例
private final static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.INSTANCE;
}
public void someService() {
// ...
}
public static void main(String[] args) {
Singleton.getInstance().someService();
}
}
|
当外部类加载时,内部类不会加载,并且静态变量只会有默认值(null)。
类的静态变量被初次访问才会触发 Java 虚拟机对该类进行初始化,会变为其初始值。
枚举单例
1
2
3
4
5
6
7
8
9
10
| public enum Singleton {
INSTANCE;
Singleton() { // 私有构造器
}
pubilc void someService() {
// 业务代码
}
}
|
字段 INSTANCE
是枚举类的唯一实例。在 Singleton.INSTANCE
初次被引用时才会被初始化。
🤩 饿汉式
1
2
3
4
5
6
7
8
9
10
| pubilc final class Singleton {
// 在创建时直接初始化,天生线程安全
private static Singleton instance = new Singleton();
println Singleton() { }
pubilc staitc Singleton getInstance() {
return instance;
}
}
|