单例模式的几个特点

  1. 为了确保全局只有一个类的实例,所以类的构造器要私有化
  2. 单例类必须由自己创建自己的唯一实例
  3. 单例类必须给其他对象提供这一个实例

单例模式是一个最简单的设计模式,属于创建型结构。比如我们window中的任务管理器,回收站,操作系统就是一个单例的设计。也就是说我们使用的对象永远是同一个。程序中的单例大致可以分为4大类。饿汉式、懒汉式、双重锁和静态内部类。我们先来说饿汉式

饿汉式

饿汉式是一个简单的写法,和名字一样。他很饿,饿的话救急着要东西。所以他会在类加载的时候就初始化。是一个线程安全的操作。但因为不是懒加载,所以不管我们用没用这个实例。他都会实例化。可能会造成一些不必要的内存。浪费内存资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @className: Singleton
* @description: 饿汉式
* @date: 2022/08/25
* @author: Sora33
*/
public class Singleton {

// 饿汉
private static Singleton singleton;

private Singleton(){}

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

懒汉式

懒汉式和饿汉式相反。支持懒加载,也就是我们使用的时候才会去实例化对象。我们可以使用synchronized来保证饿汉式的线程安全。但是加锁自然会降低效率。我们大多数情况下是用不到同步的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @className: Lazy
* @description: 懒汉式
* @date: 2022/08/25
* @author: Sora33
*/
public class Lazy {
private volatile static Lazy instance;

// 懒汉
private Lazy() {}

// 这里加上synchronized可以保证懒汉模式的线程安全
public static synchronized Lazy getInstance() {
if (instance == null) {
instance = new Lazy();
}
return instance;
}

}

双重锁(DCL)

为了解决以上两个类的弊端。我们的双重锁出现了。双重锁全名叫双重检查锁(Double checked locking of Singleton),和名字一样,会检查两次。由原本加在方法上的锁加载了内部,这样的话,只有第一次初始化的时候才会上锁。后续获取实例的时候就不会因为锁而影响性能。

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
/**
* @className: DCL
* @description: 双重检查锁
* @date: 2022/08/25
* @author: Sora33
*/
public class DCL {

// 双重检查锁
// 因为JVM具有指令重排的性质。而volatile可以起的一个主要作用是禁止指令重排。保证了多线程并发环境下的安全,也就是说强制让程序按顺序走
private static volatile DCL dclInstance;

private DCL() {}

public static DCL getDclInstance() {
// 第一次检查
if (dclInstance == null) {
// 锁住整个类
synchronized (DCL.class) {
// 第二次检查
if (dclInstance == null) {
// 初始化操作
dclInstance = new DCL();
}
}
}
return dclInstance;
}
}

静态内部类

静态内部类也是一个很好用的实现单例的方法。本质上是通过类加载机制原理实现的延迟加载。当外部类被加载的时候,内部类并没有被加载,只有我们调用getInstance()这个方法才会去加载StaticHolder()这个方法。这个时候去创建实例。并且只会被实例化一次。做到了延迟化加载,线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @className: StaticClass
* @description:
* @date: 2022/08/25
* @author: Sora33
*/
public class StaticClass {
// 静态内部类
private static class StaticHolder{
// 创建实例
private static final StaticClass INSTANCE = new StaticClass();
}

public StaticClass() {}

// 获取实例
public static StaticClass getInstance() {
return StaticHolder.INSTANCE;
}
}

总结

我们一般使用后两种方法来创建实例(双重锁&静态内部类),因为他们更安全,支持延迟化加载,效率更高并且不会浪费内存。我个人一般使用静态内部类,因为相对来说更简单一点