设计模式|单例模式

需求作用

我们一般代码里编写的时候会使用上单例,主要就是因为有些类的对象是无状态的,在程序运行期间字段的类型几乎不会发生变更,也只需要存在一份实例引用即可。同时如果出现了两个实例,就很可能会导致程序作出异常的行为。

最经典的应用就是应用程序的配置文件映射类,配置类

基于JVM类的饿汉式加载

代码实现

1
2
3
4
5
6
7
8
9
10
11
public class HungrySingleton {

private static HungrySingleton instance = new HungrySingleton();

private HungrySingleton() {
}

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

分析

这种实现方式首先是一种饿汉式加载,也就是JVM在加载类的时候就完成类对象的创建,并不是用到的时候再加载

同时也是借用了 JVM 加载类的机制来做到线程安全,并不存在线程不安全的问题:因为 JVM 加载静态类只会加载一次,并且类的静态字段初始化也是原子性的操作,因此这种实现方式天然就是线程安全的

懒汉式实现

线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LazySingleton {

private static LazySingleton instance = null;

private LazySingleton() {

}

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

}

上述实现为线程不安全的实现方式

也就是在多线程场景下,不同线程调用 LazySingleton.getInstance() 可能会创建多个实例

同步方法

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

private static SynchronizedLazySingleton instance = null;

private SynchronizedLazySingleton() {

}

//同步方法,性能较差
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}

}

上述的实现方式,在方法级别进行同步,这就会导致所有调用 getInstance 方法的线程都会阻塞住,存在性能问题

实际上我们完全可以只在实例还没存在的时候进行同步加锁,也就是这种极端场景:

① 在并发获取实例的时候, 线程A调用getInstance(), 在判断singleton == null时得到true的结果, 之后进入if语句, 准备创建instance实例

② 恰好在这个时候, 另一个线程B来了, CPU将执行权切换给了B —— 此时A还没来得及创建出实例, 所以线程B在判断singleton == null的时候, 结果还是true, 所以线程B也会进入if语句去创建实例

因此就引出了下面的双重检查锁实现 Double-check Locing

Double-check Locing

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

private static volatile DclLazySingleton instance = null;

private DclLazySingleton() {
}

public static DclLazySingleton getInstance() {
if (instance == null) {
//仅在实例还未创建的时候进行同步操作
synchronized (DclLazySingleton.class) {
//加锁后再次判断
if (instance == null) {
instance = new DclLazySingleton();
}
}
}
return instance;
}

}

静态内部类

基于静态内部类实现

原理是 JVM 在加载外部类的时候,对于静态内部类不会进行加载,只有当内部类的属性或者方法被调用的时候才会加载并进行唯一一次初始化

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 基于静态内部类实现单例模式(推荐)
* 无锁,线程安全,用到的时候才加载,并发性高
*/
public class StaticInnerClassSingleton {

private StaticInnerClassSingleton() {
}

public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.instance;
}

//静态内部类只有被用到的时候才会加载
private static class SingletonHolder {
private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}

}


设计模式|单例模式
http://example.com/2024/12/25/设计模式-单例模式/
作者
Noctis64
发布于
2024年12月25日
许可协议