单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,该模式确保一个类只有一个实例

懒汉式,线程不安全

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

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

懒汉式使用懒加载模式,但是在多线程环境中调用 getInstance() 就会创建多个实例

懒汉式,线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
class SynchronizedLazySingleton {

private static SynchronizedLazySingleton instance;
private SynchronizedLazySingleton(){}

//添加synchronized关键字
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
}

getInstance() 方法加上 synchronized 关键字实现线程安全,但是该方法效率上有问题,任何时候只能有一个线程调用 getInstance() 获取实例,而且实例第一次创建之后就不需要同步操作

饿汉式,线程安全

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

private static final HungrySingleton instance = new HungrySingleton();

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

实例被 static final 修饰,类加载时就会初始化,但它不是懒加载模式,如果 HungrySingleton 实例的创建需要某个条件参数,这种写法就不能实现

双重检验锁

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

private volatile static DoubleCheckedSingleton instance;

private DoubleCheckedSingleton() {}

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

为了避免在多线程环境中执行 instance = new DoubleCheckedSingleton() 造成指令重排序,添加 volatile 关键字修饰,禁止指令重排序优化,抛开代码可读性来说这种写法已经完美了,但是这样你就满足了吗?没有的话就接着看下去吧

静态内部类

1
2
3
4
5
6
7
8
9
10
class StaticSingleton {

private static class SingletonHolder {
private static final StaticSingleton INSTANCE = new StaticSingleton();
}
private StaticSingleton (){}
public static final StaticSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

该方法为 《Effective Java》上推荐的,它是懒汉式,并且性能没有缺陷

带参单例类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
private static Singleton Instance = null;
private Singleton(final Context context) {
//TODO
}
public static void createInstance(final Context context) {
if (Instance == null) {
synchronized (Singleton.class) {
if (Instance == null) {
Instance = new Singleton(context);
}
}
}
}
public static Singleton getInstance() {
if (Instance == null) {
throw new NullPointerException("getInstance() is Null, please call Singleton.createInstance(context) first!");
}
return Instance;
}
}

有时不得不往单例类里面传递一个上下文参数,或者初始化参数,可以用这样的单例类

枚举

1
2
3
enum  EnumSingleton {
INSTANCE
}

最简单的单例类,没有之一,还能防止序列化导致重新创建新的对象

切勿滥用单例类

  • 单例模式中没有抽象层,因此单例类的扩展很困难。类的构造函数通常是私有的,无法被继承。尤其在单元测试的时候,常常需要继承原始的类,并覆写一些方法以达到打桩的目的
  • 对需要多例的集成测试不友好,在集成测试的时候,可能需要在同一个进程里构造出两个A的实例,以方便测试
  • 代码模块之间的依赖不清晰。举例,当模块B需要使用类A的实例,它通常可以A.getInstance()来获取A的唯一实例,这样会造成整个项目代码中,到处都有A.getInstance()这样的使用,于是很难看出到底哪些模块真正依赖A。而如果B的构造函数是B(A a),就可以很直观地看出B对A的依赖

思考

以上介绍的单例模式的写法均在单一进程中有效,如果是多进程环境中,这些写法均不能保证实例的唯一性,如何在多进程环境中保证单例模式的特性呢?