实现单例模式的三种方法

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

创建单例模式的方法有很多种,这里由浅显地讲解一下常用的实现单例模式的三种方法。

饿汉式单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 饿汉式单例模式,初始化时即生成
*/
public class Singleton1 {
// 私有化构造方法使得该类无法在外部通过 new 进行实例化
private Singleton1(){
}

// 准备一个类属性(静态!!!),指向一个实例化对象。 因为是类属性,所以是单例的。
private static Singleton1 instance = new Singleton1();

// public static 方法,提供给调用者获取定义的对象
public static Singleton1 getInstance(){
return instance;
}
}

懒汉式单例模式(lazy机制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 懒汉式单例模式(lazy机制,线程安全,但效率低)
*/
public class Singleton2 {
// 私有化构造方法使得该类无法在外部通过 new 进行实例化
private Singleton2() {
}

// 声明为 private 和 static 表明该类只能在该Singleton类中被访问且只能被创建一次,实现了lazy机制
private static class SingletonHolder {
private static final Singleton2 instance = new Singleton2();
}

// 当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。
// 只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,
// 此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
public static Singleton2 getInstance() {
return SingletonHolder.instance;
}
}

双重校验锁(DCL)实现单例模式(并发环境下的单例实现方式:线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* DCL实现单例模式(并发环境下的单例实现方式)
*/
public class Singleton3 {
// 加入volatile保证可见性和原子性,保证JVM不会对指令进行重排序。
private volatile static Singleton3 instance;

private Singleton3() {}

public static Singleton3 getInstance() {
// 第一次判断,避免在instance实例已经创建的情况下进入synchronized同步代码块,提升效率
if (instance == null) {
synchronized (Singleton3.class) {
// 第二次判断,为了避免其他线程在加锁的空隙期间已创建过实例
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}

使用volatile关键字是很有必要的,因为创建对象实际上是分三步进行。对于instance = new Singleton3() 这句代码来说,分三步进行:

  1. 为instance分配内存空间
  2. 初始化instance
  3. 将instance指向分配的内存地址

如果不使用volatile关键字,由于JVM可能会进行指令重排,执行顺序可能会变。指令重排在单线程下不会出现问题,但是在多线程下就有可能会让一个线程获取到未被初始化的对象实例,这样显然就出现了问题。