Intent
確保一個類只有一個實例祭芦,并提供該實例的全局訪問點。
Class Diagram
使用一個私有構(gòu)造函數(shù)憔鬼、一個私有靜態(tài)變量以及一個公有靜態(tài)函數(shù)來實現(xiàn)龟劲。
私有構(gòu)造函數(shù)保證了不能通過構(gòu)造函數(shù)來創(chuàng)建對象實例,只能通過公有靜態(tài)函數(shù)返回唯一的私有靜態(tài)變量轴或。
Implementation
Ⅰ 懶漢式-線程不安全
以下實現(xiàn)中昌跌,私有靜態(tài)變量 uniqueInstance 被延遲實例化,這樣做的好處是照雁,如果沒有用到該類蚕愤,那么就不會實例化 uniqueInstance,從而節(jié)約資源饺蚊。
這個實現(xiàn)在多線程環(huán)境下是不安全的萍诱,如果多個線程能夠同時進入 if (uniqueInstance == null)
,并且此時 uniqueInstance 為 null污呼,那么會有多個線程執(zhí)行 uniqueInstance = new Singleton();
語句裕坊,這將導(dǎo)致實例化多次 uniqueInstance。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
Ⅱ 餓漢式-線程安全
線程不安全問題主要是由于 uniqueInstance 被實例化多次燕酷,采取直接實例化 uniqueInstance 的方式就不會產(chǎn)生線程不安全問題籍凝。
但是直接實例化的方式也丟失了延遲實例化帶來的節(jié)約資源的好處。
private static Singleton uniqueInstance = new Singleton();
Ⅲ 懶漢式-線程安全
只需要對 getUniqueInstance() 方法加鎖苗缩,那么在一個時間點只能有一個線程能夠進入該方法饵蒂,從而避免了實例化多次 uniqueInstance。
但是當(dāng)一個線程進入該方法之后酱讶,其它試圖進入該方法的線程都必須等待退盯,即使 uniqueInstance 已經(jīng)被實例化了。這會讓線程阻塞時間過程,因此該方法有性能問題得问,不推薦使用囤攀。
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
Ⅳ 雙重校驗鎖-線程安全
uniqueInstance 只需要被實例化一次软免,之后就可以直接使用了宫纬。加鎖操作只需要對實例化那部分的代碼進行,只有當(dāng) uniqueInstance 沒有被實例化時膏萧,才需要進行加鎖漓骚。
雙重校驗鎖先判斷 uniqueInstance 是否已經(jīng)被實例化,如果沒有被實例化榛泛,那么才對實例化語句進行加鎖蝌蹂。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
考慮下面的實現(xiàn),也就是只使用了一個 if 語句曹锨。在 uniqueInstance == null 的情況下孤个,如果兩個線程都執(zhí)行了 if 語句,那么兩個線程都會進入 if 語句塊內(nèi)沛简。雖然在 if 語句塊內(nèi)有加鎖操作齐鲤,但是兩個線程都會執(zhí)行 uniqueInstance = new Singleton();
這條語句,只是先后的問題椒楣,那么就會進行兩次實例化给郊。因此必須使用雙重校驗鎖,也就是需要使用兩個 if 語句捧灰。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
uniqueInstance 采用 volatile 關(guān)鍵字修飾也是很有必要的淆九。uniqueInstance = new Singleton();
這段代碼其實是分為三步執(zhí)行。
- 為 uniqueInstance 分配內(nèi)存空間
- 初始化 uniqueInstance
- 將 uniqueInstance 指向分配的內(nèi)存地址
但是由于 JVM 具有指令重排的特性毛俏,執(zhí)行順序有可能變成 1>3>2炭庙。指令重排在單線程環(huán)境下不會出先問題,但是在多線程環(huán)境下會導(dǎo)致一個線程獲得還沒有初始化的實例煌寇。例如焕蹄,線程 T1 執(zhí)行了 1 和 3,此時 T2 調(diào)用 getUniqueInstance() 后發(fā)現(xiàn) uniqueInstance 不為空唧席,因此返回 uniqueInstance擦盾,但此時 uniqueInstance 還未被初始化。
使用 volatile 可以禁止 JVM 的指令重排淌哟,保證在多線程環(huán)境下也能正常運行迹卢。
Ⅴ 靜態(tài)內(nèi)部類實現(xiàn)
當(dāng) Singleton 類加載時,靜態(tài)內(nèi)部類 SingletonHolder 沒有被加載進內(nèi)存徒仓。只有當(dāng)調(diào)用 getUniqueInstance()
方法從而觸發(fā) SingletonHolder.INSTANCE
時 SingletonHolder 才會被加載腐碱,此時初始化 INSTANCE 實例,并且 JVM 能確保 INSTANCE 只被實例化一次。
這種方式不僅具有延遲初始化的好處症见,而且由 JVM 提供了對線程安全的支持喂走。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
該實現(xiàn)在多次序列化再進行反序列化之后,不會得到多個實例谋作。而其它實現(xiàn)芋肠,為了保證不會出現(xiàn)反序列化之后出現(xiàn)多個實例,需要使用 transient 修飾所有字段遵蚜,并且實現(xiàn)序列化和反序列化的方法帖池。
該實現(xiàn)可以防止反射攻擊。在其它實現(xiàn)中吭净,通過 setAccessible() 方法可以將私有構(gòu)造函數(shù)的訪問級別設(shè)置為 public睡汹,然后調(diào)用構(gòu)造函數(shù)從而實例化對象,如果要防止這種攻擊寂殉,需要在構(gòu)造函數(shù)中添加防止實例化第二個對象的代碼囚巴。但是該實現(xiàn)是由 JVM 保證只會實例化一次,因此不會出現(xiàn)上述的反射攻擊友扰。
Examples
- Logger Classes
- Configuration Classes
- Accesing resources in shared mode
- Factories implemented as Singletons