? ? 說(shuō)到單例模式大家應(yīng)該都不陌生,就是程序在運(yùn)行過(guò)程中岛心,一個(gè)類只允許有一個(gè)實(shí)例存在于內(nèi)存當(dāng)中。例如線程池管理類碾盟、緩存管理類妆丘、某個(gè)模塊內(nèi)存管理類等锄俄。這些類之所以只允許一個(gè)實(shí)例,除了內(nèi)存消耗方面的考慮外勺拣,還有程序正確性的考量奶赠。
? ? 例如,假設(shè)圖片內(nèi)存管理類有兩個(gè)實(shí)例A和B药有,那在實(shí)例A和B上分別有兩個(gè)內(nèi)存緩存毅戈,假設(shè)把一張圖片存放在A,去B拿的時(shí)候就拿到空了愤惰,顯然不合理苇经。
? ? 單例模式要求一個(gè)類永遠(yuǎn)只能返回同一個(gè)實(shí)例,實(shí)現(xiàn)的步驟有兩個(gè):
? ? 1.將類的構(gòu)造方法的修飾符改為private宦言,這樣外部將無(wú)法實(shí)例化這個(gè)類扇单;
? ? 2.該類對(duì)外提供一個(gè)獲取實(shí)例的方法,內(nèi)部有一個(gè)指向本類的對(duì)象引用奠旺,當(dāng)引用為空時(shí)蜘澜,實(shí)例化這個(gè)類并返回,否則直接返回引用凉倚。
下面給出一些單例模式的實(shí)現(xiàn):
1.餓漢模式:在類加載的時(shí)候直接實(shí)例化【可使用】
public class Singleton {
private static volatile Singleton sInstance = new Singleton();
private Singleton() {
}
public static Singleton instance() {
return sInstance;
}
}
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單兼都,在類被加載時(shí)就實(shí)例化,避免了線程同步問(wèn)題稽寒;
缺點(diǎn):類加載時(shí)就實(shí)例化,沒(méi)有懶加載趟章,后面可能用不到這個(gè)實(shí)例杏糙,造成了內(nèi)存資源的浪費(fèi)。
2.懶漢模式(同步方法)【不推薦】
public class Singleton {
private static Singleton sInstance;
private Singleton() {}
public static synchronized Singleton instance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
優(yōu)點(diǎn):懶加載蚓土,線程同步宏侍;
缺點(diǎn):盡管JDK7對(duì)synchronized做了優(yōu)化(偏向鎖、輕量級(jí)鎖蜀漆、自旋鎖谅河、鎖去除),但是即使對(duì)象已經(jīng)實(shí)例化了,每次也都要進(jìn)行同步操作绷耍,易造成堵塞吐限,效率低。
3.懶漢模式(雙重檢查)【不推薦】
public class Singleton {
private static volatile Singleton sInstance;
private static Object mObject = new Object();
private Singleton() {
}
public static Singleton instance() {
if (sInstance == null) {
synchronized (mObject) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
優(yōu)點(diǎn):兩次check null褂始,中間加必要的同步诸典,實(shí)現(xiàn)了線程安全,創(chuàng)建了對(duì)象崎苗。接下來(lái)當(dāng)?shù)谝徊脚袛嗖粸榭盏臅r(shí)候狐粱,就直接返回了,效率也比較高胆数。
缺點(diǎn):注意到sInstance前面使用volatile關(guān)鍵字修飾了嗎肌蜻?
這里說(shuō)下題外話,JVM在new一個(gè)對(duì)象的時(shí)候必尼,有如下3個(gè)步驟:
- a. 給Singleton的實(shí)例分配內(nèi)存
- b. 調(diào)用構(gòu)造函數(shù)宋欺,初始化成員屬性字段
- c. 將sInstance指向這塊內(nèi)存
首先這個(gè)過(guò)程不是原子操作,其次JVM允許指令重排胰伍,當(dāng)執(zhí)行的順序是a - b - c的時(shí)候是沒(méi)問(wèn)題齿诞,但是當(dāng)執(zhí)行的順序是a - c - b時(shí),線程A執(zhí)行到c骂租,讓出了CPU執(zhí)行時(shí)間片祷杈,此時(shí)線程B判斷sInstance是不為空,直接返回引用渗饮,但是此時(shí)對(duì)象還沒(méi)創(chuàng)建但汞,用的時(shí)候豈不是很尷尬?NPE很有可能即將隨之而來(lái)互站。這就是DCL失效問(wèn)題私蕾,這種問(wèn)題難以跟蹤、復(fù)現(xiàn)胡桃,出現(xiàn)的概率小踩叭。
volatile能在sInstance插入內(nèi)存屏障,防止指令重排翠胰,使每次讀取都得去主內(nèi)存讀取容贝,因此能防止這樣的問(wèn)題。
但指令重排什么的是JVM為程序運(yùn)行做的優(yōu)化之一之景,這樣子做不太好斤富,并且每次都得去主內(nèi)存讀取,或多或少也影響到性能锻狗。這種優(yōu)化在《Java 高并發(fā)程序設(shè)計(jì)》等書籍上被稱為“丑陋的優(yōu)化”满力,因此不推薦使用這種焕参。
4. 靜態(tài)內(nèi)部類【推薦】
public class Singleton {
private Singleton() {
}
public static Singleton instance() {
return SHolder.sInstance;
}
private static class SHolder {
private static final Singleton sInstance = new Singleton();
}
}
這個(gè)看著跟餓漢模式有幾分相似?是懶漢加載嗎油额?
類的靜態(tài)屬性只會(huì)在第一次加載類的時(shí)候初始化叠纷,因此sInstance只會(huì)在第一次調(diào)用SHolder.sInstance,即外部調(diào)用instance()時(shí)悔耘,才會(huì)初始化讲岁。
優(yōu)點(diǎn):巧妙利用類的初始化時(shí)機(jī),避免了線程同步問(wèn)題(類在初始化時(shí)衬以,其它線程無(wú)法進(jìn)入)缓艳,同時(shí)實(shí)現(xiàn)了懶加載;
5. 枚舉【推薦】
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
借助JDK1.5中添加的枚舉來(lái)實(shí)現(xiàn)單例模式看峻。不僅能避免多線程同步問(wèn)題阶淘,而且還能防止反序列化重新創(chuàng)建新的對(duì)象』ゼ耍可能是因?yàn)槊杜e在JDK1.5中才添加溪窒,所以在實(shí)際項(xiàng)目開發(fā)中,很少見人這么寫過(guò)冯勉。