什么是單例模式
一個類在JVM只有一個實例,并且提供一個全局訪問入口。單例模式適用無狀態(tài)的工具類,比如日志工具、字符串工具;
還有全局信息類,比如全局計數(shù)、環(huán)境變量;在Java中如下類庫是適用單例模式:
-
java.lang.Runtime#getRuntime(); -
java.awt.Desktop#getDesktop(); -
java.lang.System#getSecurityManager();
單例模式的作用:節(jié)省內存;節(jié)省計算;結果的正確,比如全局計數(shù)器;方便管理。其實現(xiàn)方式很多,但不管何種實現(xiàn)方式,共同點:
- 私有的構造函數(shù);
- 私有靜態(tài)類對象;
- 公有靜態(tài)方法,唯一一個訪問私有靜態(tài)對象實例的方法。
單例模式實現(xiàn)方式
餓漢式或靜態(tài)代碼模塊式
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
另外一種變種的寫法是
/**
* 餓漢式的變種,靜態(tài)代碼形式
*/
public class StaticBlockSingleton {
private static final StaticBlockSingleton singleton;
private StaticBlockSingleton() {}
static {
singleton = new StaticBlockSingleton();
}
public StaticBlockSingleton getInstance() {
return singleton;
}
}
懶加載模式
- 線程不安全的懶加載模式
/**
* 懶漢式:只適合單線程模式
*/
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {}
public LazySingleton getInstatnce(){
if (null == singleton) {
singleton = new LazySingleton();
}
return singleton;
}
}
- 線程安全的懶加載模式
/**
* @description: 線程安全的懶加載單例模式
* @author: agentzhu
*/
public class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton singleton;
private ThreadSafeLazySingleton() {}
/**
* 加鎖粒度大,多線程環(huán)境不能同時訪問,并發(fā)效率低
* @return
*/
public synchronized ThreadSafeLazySingleton getInstatnce(){
if (null == singleton) {
singleton = new ThreadSafeLazySingleton();
}
return singleton;
}
}
雙重檢驗鎖模式(double checked locking pattern)
雙重檢驗鎖模式也是一種懶加載模式,是一種對安全型懶加載模式的優(yōu)化,具體如下:
public class Singleton {
// 關鍵點1:聲明成 volatile,禁止指令重排序
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
// 關鍵點2: 提高并發(fā)性
if (instance == null) {
synchronized (Singleton.class) {
// 關鍵點3:防止創(chuàng)建多個實例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile關鍵字的原因,new Singleton()JVM的實現(xiàn)三個不步驟,如下圖:

由于指令重排,在多線程環(huán)境中易于引起使用未初始化完全的的對象,比如下圖:

所以使用volatile關鍵字,其有兩個特性:一個是可見性;另外一個是禁止指令重排序優(yōu)化。而禁止指令重排序優(yōu)化具體來說,在volatile變量的賦值操作后面會有一個內存屏障(生成的匯編代碼上),讀操作不會被重排序到內存屏障之前。比如上面的例子,取操作必須在執(zhí)行完1-2-3 之后或者1-3-2之后,不存在執(zhí)行到1-3然后取到值的情況。
靜態(tài)內部類 static nested class
Java 5 以前的版本使用volatile的雙檢鎖還是有問題的。其原因是Java 5以前的JMM(Java 內存模型)是存在缺陷的,即時將變量聲明成 volatile 也不能完全避免重排序,主要是 volatile 變量前后的代碼仍然存在重排序問題。所以Bill Pugh提供了一種靜態(tài)內部類實現(xiàn)方式。
public class Singleton {
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
枚舉式
上面單例模式的實現(xiàn)方式都存在兩個問題:
- 序列化與反序列化創(chuàng)建多個實例;
- 反射,創(chuàng)建多個實例;
序列化與反序列化創(chuàng)建多個實例問題
public class DoubleCheckSingleton implements Serializable {
private static final long serialVersionUID = -7975945444590877513L;
// 不用volatile 修飾,會出現(xiàn)不完全初始化的狀態(tài)的實例
private static volatile DoubleCheckSingleton singleton;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance() {
if (null == singleton) { //關鍵點1: 提高并發(fā)效率
synchronized (DoubleCheckSingleton.class) {
if (null == singleton) { // 關鍵點2:防止創(chuàng)建多個實例
// 存在三個步驟,順利不是固定的[編譯器的重排序優(yōu)化],可能是:1,2,3;1,3,2
// 1.給singleton分配內存空間
// 2.調用Singleton的構造函數(shù)等來初始化singleton
// 3.將singleton對象指向分配的內存空間(執(zhí)行完此步singleton就不是null了)
singleton = new DoubleCheckSingleton();
}
}
}
return singleton;
}
private int value = 10;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
測試結果:
20
10
為此在DoubleCheckSingleton需要重寫readResolve,它會在反序列化的時候被調用,所以我們可以在此方法中返回已有的對象實例。
protected Object readResolve() {
return singleton;
}
測試結果
20
20
反射創(chuàng)建多個實例
創(chuàng)建枚舉默認就是線程安全的,所以不需要擔心double checked locking,而且還能防止反序列化導致重新創(chuàng)建新的對象。
public class SingletonReflectionDemo {
public static void main(String[] args) throws Exception {
DoubleCheckSingleton singleton = DoubleCheckSingleton.getInstance();
Constructor constructor = singleton.getClass().getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
DoubleCheckSingleton singleton2 = (DoubleCheckSingleton) constructor.newInstance();
if (singleton == singleton2) {
System.out.println("Two objects are same");
} else {
System.out.println("Two objects are not same");
}
singleton.setValue(1);
singleton2.setValue(2);
System.out.println(singleton.getValue());
System.out.println(singleton2.getValue());
}
}
測試結果
Two objects are not same
1
2
我們再來看看枚舉方式的單例實現(xiàn)方式,雖然在加載類的時候實例化,并且只有一個實例對象。存在的問題達不到懶加載的作用的。但是絕對解決上述提到的兩個問題。首先,我們來看看如何使用枚舉的方式來實現(xiàn)單例模式,然后再一一測試。
public enum EnumSingleton {
INSTANCE;
int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
- 驗證序列化與反序列化創(chuàng)建多個實例的問題
public class EnumSingletonSerializeDemo {
private static EnumSingleton instanceOne = EnumSingleton.INSTANCE;
public static void main(String[] args) {
try {
// Serialize to a file
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
"filename.ser"));
out.writeObject(instanceOne);
out.close();
instanceOne.setValue(20);
// deserialize from a file
ObjectInput in = new ObjectInputStream(new FileInputStream(
"filename.ser"));
EnumSingleton instanceTwo = (EnumSingleton) in.readObject();
in.close();
System.out.println(instanceOne.getValue());
System.out.println(instanceTwo.getValue());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- 驗證反射創(chuàng)建多個實例問題
反射進行創(chuàng)建枚舉類的會直接報錯,無法創(chuàng)建的。原因:枚舉被設計成是單例模式,即枚舉類型會由JVM在加載的時候,實例化枚舉對象,你在枚舉類中定義了多少個就會實例化多少個,JVM為了保證每一個枚舉類元素的唯一實例,是不會允許外部進行new的,所以會把構造函數(shù)設計成private,防止用戶生成實例,破壞唯一性。
An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.
總結
| 實現(xiàn)方式 | 優(yōu)點 | 缺點 |
|---|---|---|
| 餓漢式/靜態(tài)代碼模塊式 | 簡單,在類加載的時完成實例化;無線程同步問題 | 不使用此實例,也會在類加載的時候完成實例化,浪費內存;存在序列化、反射創(chuàng)建多個實例問題 |
| 懶加載或者線程安全的懶加載式 | 獲取實例的時候才初始化,但只適合單線程情況下使用 | 線程不安全或者并發(fā)度低;存在序列化、反射創(chuàng)建多個實例問題 |
| 雙重檢驗鎖模式 | 線程安全;懶加載; | 代碼復雜度高,易寫錯;存在序列化、反射創(chuàng)建多個實例問題 |
| 枚舉式 | 線程安全;代碼簡單,流行度不高 | 不是懶加載,不存在序列化,反射創(chuàng)建多個實例問題 |