单例模式是什么?

单例模式(Singleton Pattern)是一种创建型的设计模式,它的目标是确保某一个类在 JVM 中有且仅有一个实例对象,并且这个类会提供一个全局的访问点。在 Java 中,有多种方式可以实现单例模式,下面将逐一介绍。

饿汉式单例

对于普通的类而言,实现单例通常需要确保以下三点:

  • 构造器私有化;
  • 静态的字段引用唯一的实例;
  • 提供一个静态的工厂方法来获取该实例。

饿汉式单例指的是在装载单例类时就实例化该单例对象,例如:

  1. public class HungrySingleton {
  2. private HungrySingleton() { }
  3. private static HungrySingleton singleton = new HungrySingleton();
  4. public static HungrySingleton getInstance() {
  5. return singleton;
  6. }
  7. // other fields and methods...
  8. }

这种方式的优点是实现简单,缺点是可能会造成资源的浪费。因为 JVM 在装载 HungrySingleton 类时就会去实例化 singleton 对象,如果程序并不需要使用 singleton 对象,那么 singleton 对象在实例化过程中占用的资源以及消耗的时间则都是浪费的。

懒汉式单例

不同于饿汉式单例,懒汉式单例是以延迟加载的方式,在初次获取单例对象时,才去实例化它。例如:

  1. public class LazySingleton {
  2. private LazySingleton() { }
  3. private static LazySingleton singleton;
  4. public synchronized static LazySingleton getInstance() {
  5. if (singleton == null) {
  6. singleton = new LazySingleton();
  7. }
  8. return singleton;
  9. }
  10. // other fields and methods...
  11. }

为了确保线程安全,我们使用了 synchronized 关键字修饰了静态工厂方法。虽然懒汉式单例解决了资源浪费的问题,但它却可能引起新的问题:执行效率低。由于 synchronized 修饰的是 getInstance 整个方法,如果该方法被频繁调用,频繁的线程同步将会严重影响性能。

双检锁单例

为了解决上述由 synchronized 修饰整个静态工厂方法引起的性能问题,我们采取优化措施:先尝试获取单例对象,如果获取不到实例,才去获取锁以实例化对象,在获得锁之后需要再进行一次检查,以保证操作的原子性。例如:

  1. public class DclSingleton {
  2. private DclSingleton() { }
  3. private static volatile DclSingleton singleton;
  4. public static DclSingleton getInstance() {
  5. if (singleton == null) {
  6. synchronized (DclSingleton.class) {
  7. if (singleton == null) {
  8. singleton = new DclSingleton();
  9. }
  10. }
  11. }
  12. return singleton;
  13. }
  14. // other fields and methods...
  15. }

注意,采用这种方式单例,必须用 volatile 修饰实例字段 singleton 以避免缓存不一致的问题。虽然这种方式有效地规避了线程安全和性能的问题,但它也存在缺陷:

  • 使用了 volatile 关键字,在 Java 1.4 及以下版本不可采用此方式;
  • 代码相当冗长且难以阅读。

静态内部类单例

上述的实现方式各有利弊,现在将介绍一种更为优雅的实现方式,利用静态内部类达到延迟加载的效果,同时避免繁琐的线程锁操作。

  1. public class StaticInnerSingleton {
  2. private StaticInnerSingleton() { }
  3. private static class SingletonHolder {
  4. public static final StaticInnerSingleton instance = new StaticInnerSingleton();
  5. }
  6. public static StaticInnerSingleton getInstance() {
  7. return SingletonHolder.instance;
  8. }
  9. // other fields and methods...
  10. }

这种实现方式汲取了以上三种方式的优点:实现简单、延迟加载、执行高效以及线程安全。

枚举单例

上述介绍的方式都是利用私有的构造器实现单例,当要使得类可序列化时,仅仅在声明中加上 implements Serializable 是不够的。为了确保单例,必须将所有的成员字段都声明为瞬时的(transient),并且提供一个
readResolve 方法。否则,每次反序列化时,都会创建一个新的实例。

  1. private Object readResolve() {
  2. return singleton;
  3. }

Joshua Block 在《Effective Java》一书中,提出了一种更为巧妙的方式:以枚举实现单例。

  1. public enum EnumSingleton {
  2. INSTANCE("VALUE");
  3. private String value;
  4. private EnumSingleton(String value) {
  5. this.value = value;
  6. }
  7. // getter and setter
  8. }

这种方法非常简洁,而且无偿地提供了序列化机制,绝对防止多次实例化。

参考资料

以下是本文参考的资料: