學(xué)習(xí)《Android 源碼設(shè)計(jì)模式解析與實(shí)踐》系列筆記
什么是單例
單例模式是應(yīng)用最廣残腌,也是最容易理解的模式之一村斟。
在它的核心結(jié)構(gòu)中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統(tǒng)中抛猫,應(yīng)用該模式的一個類只有一個實(shí)例蟆盹。即一個類只有一個對象實(shí)例。
定義
《設(shè)計(jì)模式》(艾迪生維斯理, 1994)中的定義:“保證一個類僅有一個實(shí)例闺金,并提供一個訪問它的全局訪問點(diǎn)逾滥。”
Java 中單例模式定義:“一個類有且僅有一個實(shí)例败匹,并且自行實(shí)例化向整個系統(tǒng)提供寨昙。”
使用場景
為了避免某個類創(chuàng)建多個對象而造成資源的消耗掀亩,或者是這個類型的對象應(yīng)該且只有一個舔哪。
結(jié)構(gòu)
單例模式要點(diǎn):
- 構(gòu)造行數(shù)不對外開發(fā),需要設(shè)置為
privte
; - 單例類自行實(shí)例化一個對象槽棍;
- 外部能通過一個靜態(tài)方法或者是枚舉拿到該單例類對象尸红;
- 確保單例類對象唯一。
實(shí)現(xiàn)
單例的實(shí)現(xiàn)有多重方式:
1. 餓漢模式
public class Singleton {
private static final Singleton sInstance = new Singleton();
private Singleton() {}
public static Singleton getsInstance() {
return sInstance;
}
}
餓漢模式因?yàn)槭琴x值的靜態(tài)變量,所以會在類加載的時候就進(jìn)行了初始化外里,也就是這時已經(jīng)創(chuàng)建好了靜態(tài)的單例對象怎爵。
這個對象是唯一的,也是線程安全的盅蝗。
缺點(diǎn):
過早初始化鳖链,占用資源。
2. 懶漢模式
public class Singleton {
private static Singleton sInstance = null;
private Singleton() {}
public static synchronized Singleton getsInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
懶漢模式在會獲取實(shí)例的時候才進(jìn)行初始化墩莫,較餓漢模式節(jié)約資源芙委。但是為了保證多線程下第一次調(diào)用時實(shí)例化對象的唯一性,加了 synchronized
關(guān)鍵字狂秦,這個在初始化后的同步都是沒有必要的灌侣,因此會造成不必要的同步開銷。
3. 雙重校驗(yàn)鎖(double check lock)模式
public class Singleton {
private static Singleton sInstance = null;
private Singleton() {}
public static Singleton getsInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
可以看到裂问,雙重校驗(yàn)鎖模式是在 2 懶漢模式的基礎(chǔ)上的優(yōu)化侧啼。
同步鎖不再加在外層函數(shù)上,而是加在了初始化模塊上堪簿。這樣既保證了線程安全問題痊乾,又解決了初始化后同步消耗問題。
這種模式的關(guān)鍵點(diǎn)是在兩個判空上面椭更,第一次的判空是為了避免不必要的同步哪审,而第二步是為了確保不被多次實(shí)例化。
例如:A 線程和 B 線程同時執(zhí)行到 synchronized (Singleton.class) 這一步虑瀑,A 拿到了鎖湿滓,(B 則會等待 A 執(zhí)行完后釋鎖)然后執(zhí)行了 sInstance = new Singleton()。這時舌狗,A 執(zhí)行完了茉稠,并且實(shí)例化了 sInstance 了,然后釋放鎖后 B 拿到了鎖把夸,B 繼續(xù)往下執(zhí)行而线,假設(shè)這時沒有第二次判空,那 B 會再次實(shí)例化一個 sInstance 對象恋日,這樣 A 和 B 拿到的對象就不是同一個了膀篮。所以說第二次的判空是必須的,這時用來保證實(shí)例化對象的唯一性的岂膳。
然而誓竿,兩次判空就真的能保證實(shí)例化對象的唯一性了嗎?
答案是否定的谈截。
在 A 執(zhí)行到 Instance = new Singleton()時筷屡,看似是一句代碼涧偷,但實(shí)際上它不是一個原子操作,這句代碼最終會被編譯成多條匯編指令(真實(shí)淡疼)毙死,匯編指令大概做了下面三個操作:
(1) 給 Singeton 的實(shí)例分配內(nèi)存燎潮;
(2) 調(diào)用 Singleton() 的構(gòu)造函數(shù),初始化成員字段扼倘;
(3) 將 sInstance 對象指先分配的 內(nèi)存空間(此時 sInstance 就不是 null 了
)确封。
可以看到,如果是順序執(zhí)行的再菊,也是沒有問題的爪喘,問題是在 JVM 1.5
之前,2纠拔,3 步的順序是不能保證的秉剑,也就是可以是 1-2-3的順序執(zhí)行,也可能是 1-3-2 的執(zhí)行順序侦鹏。所以,但是如果是后面這種情況,在 A 線程已經(jīng)執(zhí)行完 1-3 步時匹耕,sInstance 已經(jīng)非空了荠雕,這時 B 線程執(zhí)行 getInstance 方法發(fā)現(xiàn)是非空的,直接拿走 sInstance 使用炸卑,就會導(dǎo)致出錯。
好的是嘱蛋,JVM 1.5
之后五续,可以通過 volatile
解決此問題。
雙重校驗(yàn)鎖模式資源利用率高凶伙,但是也同樣存在第一次加載反應(yīng)稍慢的問題它碎。
4. 靜態(tài)內(nèi)部類模式
public class Singleton {
private Singleton() {}
public static Singleton getsInstance() {
return Holder.sInstance;
}
private static class Holder {
private static final Singleton sInstance = new Singleton();
}
}
巧妙利用了虛擬機(jī)加載靜態(tài)內(nèi)部類的時機(jī)和方式显押,不僅確保了線程安全傻挂,也保證實(shí)例對象的唯一性。
sInstance 在第一次調(diào)用 getInstance 方法時才初始化蝉仇,所以也是延遲初始化殖蚕。
這種模式是最推薦的實(shí)現(xiàn)方式。
5. 枚舉模式
public enum SingletonEnum {
SINGLETON;
public void method() {
...
}
}
枚舉類是實(shí)現(xiàn)單例的最簡單的模式害驹。
枚舉和 Java
的普通類一樣蛤育,能有字段宛官、方法底洗,而且默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的咕娄,并且在任何情況下都是一個單例。
總結(jié)
單例模式是設(shè)計(jì)模式里面最基礎(chǔ)的也是使用較為頻繁的模式圣勒。
相關(guān)文章:
設(shè)計(jì)模式整理(1) 代理模式
設(shè)計(jì)模式整理(2) 單例模式
設(shè)計(jì)模式整理(3) Builder 模式
設(shè)計(jì)模式整理(4) 原型模式
設(shè)計(jì)模式整理(5) 工廠模式
設(shè)計(jì)模式整理(6) 策略模式
設(shè)計(jì)模式整理(7) 狀態(tài)模式
設(shè)計(jì)模式整理(8) 責(zé)任鏈模式
設(shè)計(jì)模式整理(9) 觀察者模式
設(shè)計(jì)模式整理(10) 適配器模式
設(shè)計(jì)模式整理(11) 裝飾模式
設(shè)計(jì)模式整理(12) 中介者模式