设计模式->单例模式

1. 前言

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点,有很多种实现方式

  • 饿汉式,也就是类初始化时直接创建对象
  • 懒汉式,在调用函数时创建对象
  • 静态内部类,在外部类初始化时静态内部类并不会被初始化,延迟初始化类获取单例实例
  • 枚举

他们都有一个特性,构造器私有化

2.实现

2.1 饿汉式

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private Singleton() {
}
// 类加载时就初始化
private static final Singleton singleton = new Singleton();

public static Singleton getInstance() {
return singleton;
}

}

这种方式最直接,在类装载时就会实例化对象,不会存在线程安全问题,可能不符合某些需求

2.2 懒汉式

2.2.1 线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton() {
}

private static Singleton singleton = null;

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

2.2.2 线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton() {
}

private static Singleton singleton = null;

public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private Singleton() {
}

private static Singleton singleton = null;

public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
}

效率低,所以引出 DCL

2.3 DCL(Dobule Check Locking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {
private Singleton() {
}

private static volatile Singleton singleton = null;

public static Singleton getInstance() {
// 加一层减少同步的资源损耗
if (singleton == null) {
synchronized (Singleton.class) {
// 此时可能有多个线程进入一个if判断 所以得第二次检测
if (singleton == null) {
// volatile 防止CPU指令重排优化,造成初始化顺序有问题,初始化为null
singleton = new Singleton();
}
}
}
return singleton;
}
}

2.4 静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private Singleton() {
}

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

static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}

利用静态内部类延迟初始化机制,实现懒加载,并且不会出现线程安全问题

2.5 反射破坏

上面方式尽管解决了一些问题,但还是免不了被反射破坏,反射是 Java 一个强大的特性,下面用反射破坏一下单例模式

1
2
3
4
5
6
7
8
9
10
11
12
Singleton instance = Singleton.getInstance();
System.out.println(instance);
// 获取class
Class<? extends Singleton> aClass = instance.getClass();
// 获取私有构造
Constructor<? extends Singleton> constructor = aClass.getDeclaredConstructor();
// 设置访问权限
constructor.setAccessible(true);
// 实例化
Singleton singleton = constructor.newInstance();

// 最后获得新实例,违反了单例初衷,但是没有办法(没法杜绝)

2.6 枚举

枚举是一个特殊的类,一般表示常量,它也可以实现单例,并且是一个很好的选择

在上面讲过了几种单例实现方式,都会被反射破坏,但是枚举不会

image-20210821150658317

无法通过反射创建枚举对象,我们可以用这个特性去实现单例模式

1
2
3
4
5
6
7
public enum Singleton {
INSTANCE;

Singleton() {

}
}

单例模式的使用场景还是很多的 ,最常见的就是强大的 Spring框架,然后计数,缓存等等