定义

单例模式指全局应用中只有一个实例

  • 构造方法一般为 private
  • 通过静态方法或者枚举返回实例对象
  • 确保在多线程环境下只有一个实例产生
  • 确保实例在序例化和反序例化时不会产生新的实例

饿汉模式

1
2
3
4
5
6
7
8
9
public class Singleton {
    public static final Singleton instance = new Singleton();
  
  	private Singleton() {}
  
  	public static Singleton getInstance() {
     		return instance;  
    }
}

在类的内部有一个静态变量 instance, 静态变量会在类加载时被初始化, 在多线程的情况下也会拿到同样实例对象.

饿汉模式的缺点在于程序开始时, 类在被加载后实例马上被初始化, 如果没有用到该实例, 会消耗更多的资源.

懒汉模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Singleton {
 	public static final Singleton instance;
  
  	private Singleton() {}
  
  	public static synchronized Singleton getInstance() {
        if (instance == null) {
     				instance = new Singleton();
        }
      	return instance;
    }
}

懒汉模式节约了资源, 只会在第一次调用 getInstance 时才会初始化实例, 同时使用 synchronized 保证了线程同步问题.

懒汉模式的缺点主要在于每次获取实例都需要同步线程锁, 造成很大的不必要开销

Double Check Lock (DCL)模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Singleton {
 	public volatile static Singleton instance = null;
  
  	private Singleton() {}
  
  	public static Singleton getInstance() {
        if (instance == null) {
          	synchronized(Singleton.class) {
              	if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
      
      	return instance;
    }
}

双锁检查模式指的是在获取实例时, 两次检查实例是否存在

第 7 行中的第一次检查, 如果实例存在, 可以避免不必要的线程同步开销

第 9 行中的第二次检查当实例为 null 时才创建对象, new 一个对象并不是一个原子操作, 可以分为大概三步:

  1. Singleton 的实例对象分配内存空间

  2. 调用 Singleton 的构造方法来初始化实例

  3. instance 变量指向该块内存

由于 Java 允许处理器可以乱序执行指令, 则上面的可能是 1 -> 2 -> 3 或 1 -> 3 -> 2 这样的顺序来执行. 如果以 1 -> 3 -> 2 的顺序, 当执行到步骤 3 时, 处理器切换到别的线程 B, 这时正好执行到获取实例的第 7 行, 此时实例对象不为 null, 该方法便会返回, 但实例只是分配了内存空间却没有初始化

使用关键字 volatile 修饰成员变量 instance, 这样在获取该变量时总是从主内存中获取, 可以保证程序的正确性

第二检查也确保了在以下情况时, 程序的正确性. 线程 A 存在创建实例, 此时切换到线程 B, 线程 B 被阻塞在第 8 行, 线程 A 执行完后, 实例化完成, 此时线程 A 获得锁, 检查实例已经存在, 就不会再次创建实例了

静态内部类单例模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Singleton {
  	private Singleton() {}
  
  	public static Singleton getInstance() {
        return InnerSingleton.instance;
    }
  
  	private static class InnerSingleton {
        private static final Singleton instance = new Singleton();
    }
}

双检查模式由于使用 volatile 会造成更多的开销, 并且在某些情况下会造成 DCL 失效, 静态内部类的单例模式完美的解决了线程同步, 资源开销问题

程序在第一次调用 getInstance 时才会把内部类 InnerSingleton 加载到 JVM 中, 此时才会初始化内部静态变量 instance

枚举类单例

1
2
3
4
5
6
7
public enum SingletonEnum {
 	INSTANCE;
  
  	public void func() {
      	// do something...
    }
}

枚举类都是线程安全的, 在自定义的枚举类添加所需要的方法, 便可以轻松得到一个单例实现.

更重要的是枚举类在序列化反序列化时, 都只会产生一个实例, 其他普通类则需要实现 readResolve 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Singleton implements Serializable {
	private static final long serialVersionUID = 0L;  
  
 	public static final Singleton instance;
  
  	private Singleton() {}
  
  	public Object readResolve() throws ObjectStreamException {
      	return instance;
    }
}

实现 readResolve 方法, 让其返回我们设计好的实例对象, 这样反序列时就不会生成新的实例对象了