单例模式

意图:

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

问题:

  1. 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

  2. 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

    还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

解决方案:

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

适用性:

当满足以下情况时,使用单例模式:

  • 确保一个类只有一个实例,并且客户端能够通过一个众所周知的访问点访问该实例。
  • 唯一的实例能够被子类扩展, 同时客户端不需要修改代码就能使用扩展后的实例。

一些典型的单例模式用例包括:

  • logging
  • 管理与数据库的链接
  • 文件管理器(File manager

已知使用:

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

代码示例:

饿汉式:

饿汉式的单例实现比较简单,其在类加载的时候,静态实例instance 就已创建并初始化好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class Singleton {

private Singleton() {

}

private static final Singleton INSTANCE = new Singleton();


public static Singleton getInstance() {
return INSTANCE;
}

}

测试:

1
2
3
4
5
6
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println("singleton1=" + singleton1);
System.out.println("singleton2=" + singleton2);
}

结果:

1
2
singleton1=com.design.patterns.Singleton@74a14482
singleton2=com.design.patterns.Singleton@74a14482

懒汉式(线程安全的懒加载方式):

与饿汉式对应的是懒汉式,懒汉式为了支持延时加载,将对象的创建延迟到了获取对象的时候,但为了线程安全,不得不为获取对象的操作加锁,这就导致了低性能。

并且这把锁只有在第一次创建对象时有用,而之后每次获取对象,这把锁都是一个累赘。

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
public class ThreadSafeLazyLoadedSingleton {

/**
* declared as volatile to ensure atomic access by multiple threads.
*/
private static volatile ThreadSafeLazyLoadedSingleton instance;

/**
* Private constructor to prevent instantiation from outside the class.
*/
private ThreadSafeLazyLoadedSingleton() {
// Protect against instantiation via reflection
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}

/**
* The instance doesn't get created until the method is called for the first time.
*/
public static synchronized ThreadSafeLazyLoadedSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeLazyLoadedSingleton();
}
return instance;
}

}

测试:

1
2
3
4
ThreadSafeLazyLoadedSingleton instance1 = ThreadSafeLazyLoadedSingleton.getInstance();
ThreadSafeLazyLoadedSingleton instance2 = ThreadSafeLazyLoadedSingleton.getInstance();
System.out.println("instance1=" + instance1);
System.out.println("instance2=" + instance2);

结果:

1
2
instance1=com.design.patterns.singleton.ThreadSafeLazyLoadedSingleton@74a14482
instance2=com.design.patterns.singleton.ThreadSafeLazyLoadedSingleton@74a14482

双重检测:

饿汉式和懒汉式的单例都有缺点,双重检测的实现方式解决了这两者的缺点。

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
32
33
34
35
36
37
public class ThreadSafeDoubleCheckSingleton {

/**
* declared as volatile to ensure atomic access by multiple threads.
*/
private static volatile ThreadSafeDoubleCheckSingleton instance;

/**
* Private constructor to prevent instantiation from outside the class.
*/
private ThreadSafeDoubleCheckSingleton() {
// Protect against instantiation via reflection
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}

/**
* To be called by user to obtain instance of the class.
*/
public static ThreadSafeDoubleCheckSingleton getInstance() {
// local variable increases performance by 25 percent
ThreadSafeDoubleCheckSingleton result = instance;
// Check if singleton instance is initialized.
if (instance == null) {
synchronized (ThreadSafeDoubleCheckSingleton.class) {
result = instance;
if (result == null) {
result = new ThreadSafeDoubleCheckSingleton();
instance = result;
}
}
}
return result;
}

}

测试:

1
2
3
4
5
6
public static void main(String[] args) {
ThreadSafeDoubleCheckSingleton instance1 = ThreadSafeDoubleCheckSingleton.getInstance();
ThreadSafeDoubleCheckSingleton instance2 = ThreadSafeDoubleCheckSingleton.getInstance();
System.out.println(instance1);
System.out.println(instance2);
}

结果:

1
2
com.design.patterns.singleton.ThreadSafeDoubleCheckSingleton@74a14482
com.design.patterns.singleton.ThreadSafeDoubleCheckSingleton@74a14482

静态内部类:

这种方式能达到双检锁方式一样的功效,但实现更简单。

这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance

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
public final class StaticInnerClassSingleton {

/**
* Private constructor to prevent instantiation from outside the class.
*/
private StaticInnerClassSingleton() {

}

/**
* The InstanceHolder is a static inner class, and it holds the Singleton instance.
* It is not loaded into memory until the getInstance() method is called.
*/
private static class InstanceHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}

/**
* When this method is called, the InstanceHolder is loaded into memory
* and creates the Singleton instance. This method provides a global access point
* for the singleton instance.
*/
public static StaticInnerClassSingleton getInstance() {
return InstanceHolder.INSTANCE;
}

}

枚举:

一个只有一个元素的枚举类型是实现单例模式的最佳方式。

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

@Override
public String toString() {
return getDeclaringClass().getCanonicalName() + "@" + hashCode();
}
}

测试:

1
2
3
4
5
6
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1);
System.out.println(instance2);
}

结果:

1
2
com.design.patterns.singleton.EnumSingleton@1956725890
com.design.patterns.singleton.EnumSingleton@1956725890

影响

  • 通过控制实例的创建和生命周期,违反了单一职责原则(SRP)。
  • 鼓励使用全局共享实例,组织了对象及其使用的资源被释放。
  • 代码变得耦合,给客户端的测试带来难度。
  • 单例模式的设计可能会使得子类化(继承)单例变得几乎不可能