Intro

单例模式\((Singleton)\)可以说是\(23\)种设计模式里比较容易理解的其中一种了,但这并不意味着单例模式就很简单,因为这种设计模式的实现方式比较多变。
我会以以下顺序来逐一了解单例模式的实现方式:

  • 普通实现
  • 枚举实现
  • \(IoDH\)(Initialization On Demand Holder)实现
  • 懒加载实现
  • 双重检查锁实现(Double Check Locking)

普通实现

最普通的单例模式的实现比较简单,以\(Java\)来讲,我们需要将单例类的构造器置为私有,让单例类自行保存维护自己的变量,我们需要提供一个公开的静态的方法来将这个单例类的私有对象暴露出去,值得一提的是,这样的做法是线程安全的。


这里说明一点:可能有部分同学在网上看到的单例的普通实现其中静态成员是如下的这种写法:public static final Object instance = new Object();关于我这里将静态成员置为private的原因是,虽然public这种形式已经非常好了,已经满足的单例模式的要求:保证单例类一旦被实例化,只会存在一个单例类的实例,不多也不少。但是有一点,public这种实现在享有特权的客户端下可以通过
AccessibleObject.setAccessible()方法来访问单例类的私有构造器,为了抵御住这种攻击,我们需要将静态成员置为private

代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package net.sunzhenyu.miscellaneous.pattern.singleton;

/**
* Simple Singleton pattern impl
*
* @author SunZhenyu
* @version 0.0.1
* @date 2018-07-06
* @since 1.8
*/
public class Singleton {

private static final Singleton instance = new Singleton();

private Singleton() {
}

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

单元测试:
simple-code
simple-result

枚举实现

枚举实现单例模式,在Effective Java 2nd Edition一书中有介绍过,枚举实现是单例模式实现里最简单的一种。这种实现是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
package net.sunzhenyu.miscellaneous.pattern.singleton;

/**
* Enum based singleton impl
*
* @author SunZhenyu
* @version 0.0.1
* @date 2018-07-06
* @since 1.8
*/
public enum EnumSingleton {
INSTANCE;
}

单元测试:
enum-code
enum-result

\(IoDH\)(Initialization On Demand Holder)实现

在这种被称之为Initialization On Demand Holder的实现方法中,我们需要给单例类增加一个静态内部类,单例类的引用将会由静态内部类管理,同样的,我们需要私有化构造器、提供获取单例类的方法。

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
package net.sunzhenyu.miscellaneous.pattern.singleton;

/**
* Initialization on demand holder impl
*
* @author SunZhenyu
* @version 0.0.1
* @date 2018-07-06
* @since 1.8
*/
public class IODHSingleton {

private IODHSingleton() {
}

private static class SingletonHolder {

private static final IODHSingleton instance = new IODHSingleton();
}

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

}

单元测试:
iodh-code
iodh-result

懒加载实现

懒加载的单例实现方式如其名,在我们需要单例类的实例的时候再去加载的,而不是在加载类的时候就去实例化他。因为是懒加载(按需去实例化),那么我们也需要对在多线程下的情况做考虑,这里我们可以使用synchronized修饰符去修饰getInstance()方法。


此处需要注意的一点是,我们在懒加载实现中仅仅将单例类的构造器私有化是不行的,在某些极端特殊的情况下,我们仍可以通过反射来调用单例类的构造器,这里我们需要在构造器中加入一些防御性的代码,来防止这种情况的发生。

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
package net.sunzhenyu.miscellaneous.pattern.singleton;

/**
* Lazy loaded singleton impl
*
* @author SunZhenyu
* @version 0.0.1
* @date 2018-07-07
* @since 1.8
*/
//@formatter:off
public final class LazyLoadedSingleton {

private static LazyLoadedSingleton instance;

private LazyLoadedSingleton() {
if (instance == null)
instance = this;
else
throw new IllegalStateException("Already initialized!");
}

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

单元测试:
lazy-loaded-code
lazy-loaded-result

双重检查锁实现(Double Check Locking)

在\(Java 5\)之前,双重检查锁的单例实现一直都存在一个问题,在解决这个问题之前我们先来看下双重检查锁的实现:

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
30
31
package net.sunzhenyu.miscellaneous.pattern.singleton;

/**
* Double check locking singleton impl
*
* Broken under JDK 1.5
*
* @author SunZhenyu
* @version 0.0.1
* @date 2018-07-07
* @since 1.8
*/
//@formatter:off
public class DoubleCheckLockingSingleton {

private static DoubleCheckLockingSingleton instance;

private DoubleCheckLockingSingleton() {
if (instance != null)
throw new IllegalStateException("Already initialized!");
}

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

这个实现乍一看是没什么问题的,Emmm…,完美的双重检查锁实现,可是真的是这样吗?但是很遗憾的是,上面这种实现,在\(Java 5\)之前,在多线程的环境下,可能会出现一个线程看到了个初始化到了一半的instance的情况。
那么,为了防止这种情况,我们需要对单例类的私有静态成员用volatile修饰来保证:

  • 不同线程对这个单例类的私有静态成员进行操作的可见性,即使其中一个线程修改了它的值,这个修改后的值对于其他的线程也是立即可见的。
  • 同时禁止了\(JVM\)对这个单例类的私有静态成员进行指令重排序。

修改后的双重检查锁单例实现:

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
30
31
package net.sunzhenyu.miscellaneous.pattern.singleton;

/**
* Fixed double check locking singleton impl
*
* @author SunZhenyu
* @version 0.0.1
* @date 2018-07-07
* @since 1.8
*/
//@formatter:off
public final class FixedDoubleCheckLockingSingleton {

private volatile static FixedDoubleCheckLockingSingleton instance;

private FixedDoubleCheckLockingSingleton() {
if (instance != null)
throw new IllegalStateException("Already initialized!");
}

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

单元测试:
double-check-locking-code
double-check-locking-result

单例模式固然容易理解,但是它的实现方式众多,我们需要了解单例模式各个实现的优劣之处,以及我们可以从单例模式入手,逐渐感受到设计模式的优雅之处。