單例模式

前言

HI,歡迎來(lái)到《每周一博》蛾派。今天是十月第五周俄认,我給大家簡(jiǎn)單介紹一下單例模式。

單例模式是最簡(jiǎn)單的模式洪乍,也是應(yīng)用最廣的模式之一眯杏。有時(shí)整個(gè)系統(tǒng)只需要一個(gè)全局對(duì)象,這樣有利于協(xié)調(diào)系統(tǒng)整體的行為壳澳,比如硬件資源岂贩,數(shù)據(jù)庫(kù)等,這種不能由用戶自由創(chuàng)建對(duì)象的場(chǎng)景巷波,就適合使用單例模式萎津。

單例設(shè)計(jì)模式的優(yōu)點(diǎn):

  1. 在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支抹镊,尤其是當(dāng)一個(gè)對(duì)象需要頻繁創(chuàng)建銷毀锉屈,并且創(chuàng)建或銷毀時(shí)性能無(wú)法優(yōu)化,單例模式的優(yōu)勢(shì)就非常明顯垮耳;
  2. 單例模式只生成了一個(gè)實(shí)例颈渊,減少了系統(tǒng)性能開銷,當(dāng)一個(gè)對(duì)象的產(chǎn)生需要較多資源時(shí),如讀取配置儡炼,產(chǎn)生其他依賴對(duì)象時(shí)妓湘,可以通過(guò)應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象查蓉,然后永久駐留內(nèi)存的方式來(lái)解決驶沼;
  3. 單例模式可以避免對(duì)資源的多重占用魄梯,比如避免對(duì)同一個(gè)文件同時(shí)進(jìn)行寫操作;
  4. 單例模式可以在系統(tǒng)設(shè)置全局的訪問(wèn)點(diǎn),優(yōu)化和共享資源訪問(wèn)呕寝,比如設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理橙垢,安卓源碼的系統(tǒng)服務(wù)就是這么用的豺憔;

單例設(shè)計(jì)模式的缺點(diǎn):

  1. 沒(méi)有抽象層接口,擴(kuò)展困難霜浴,只能修改代碼晶衷,不適用于變化對(duì)象;
  2. 如果持有Context引用要使用Application Context阴孟,否則會(huì)造成內(nèi)存泄漏晌纫;

實(shí)現(xiàn)單例模式需要注意的地方:
A. 保證構(gòu)造函數(shù)是私有的;
B. 對(duì)外提供一個(gè)靜態(tài)方法來(lái)返回對(duì)象永丝;
C. 要保證線程安全锹漱;
D. 反序列化也要保證對(duì)象唯一;

單例模式的實(shí)現(xiàn)主要有3種:

  1. 餓漢式:
    直接創(chuàng)建對(duì)象
// 直接創(chuàng)建靜態(tài)變量
public class Singleton {
     private static Singleton instance = new Singleton();
     private Singleton(){}
     public static synchronized Singleton getInstance(){
         return instance;
     }
 }

// 或者通過(guò)靜態(tài)初始化塊
public class Singleton {
    private static Singleton instance ;
    static {
        instance = new Singleton();
    }
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        return instance;
    }
}

優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單慕嚷,線程安全哥牍;
缺點(diǎn):不管該類有沒(méi)有用到都會(huì)創(chuàng)建對(duì)象,消耗資源

  1. Double CheckLock:
    線程安全的懶漢式喝检,對(duì)象需要時(shí)才進(jìn)行創(chuàng)建
public class Singleton {
    private static volatile Singleton instance;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance ==null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

該實(shí)現(xiàn)方式會(huì)增加同步鎖來(lái)保證線程安全嗅辣,注意先判空,再同步鎖挠说,再判空澡谭,這樣就能夠做到效率和安全的雙重保證。那么為什么要進(jìn)行2次判空呢纺涤?

new一個(gè)對(duì)象并不是一個(gè)原子操作译暂,它會(huì)編譯成多條匯編指令,主要做了3件事:

  1. 給對(duì)象實(shí)例分配內(nèi)存撩炊;
  2. 調(diào)用構(gòu)造函數(shù)外永,初始化成員字段;
  3. 將對(duì)象紙箱分配的內(nèi)存空間拧咳,此時(shí)對(duì)象就不是null了伯顶;

由于Java編譯器允許處理器亂序執(zhí)行,所以第2步和第3步的先后順序是不確定的,當(dāng)兩個(gè)線程同時(shí)到達(dá)后就可能會(huì)創(chuàng)建2個(gè)實(shí)例祭衩。

Java語(yǔ)言提供了一種稍弱的同步機(jī)制灶体,即volatile變量,用來(lái)確保將變量的更新操作通知到其他線程掐暮。當(dāng)把變量聲明為volatile類型后蝎抽,編譯器與運(yùn)行時(shí)都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序路克。volatile變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見的地方樟结,因此在讀取volatile類型的變量時(shí)總會(huì)返回最新寫入的值。

在訪問(wèn)volatile變量時(shí)不會(huì)執(zhí)行加鎖操作精算,因此也就不會(huì)使執(zhí)行線程阻塞瓢宦,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級(jí)的同步機(jī)制。

  1. 靜態(tài)內(nèi)部類:
    利用靜態(tài)類只會(huì)加載一次的機(jī)制灰羽,使用靜態(tài)內(nèi)部類持有單例對(duì)象驮履,達(dá)到單例的效果
public class Singleton {
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        return SingletonHolder.instance;
    }
    private static class SingletonHolder{
        private static Singleton instance = new Singleton();
    }
}

靜態(tài)內(nèi)部類的優(yōu)點(diǎn):
外部類加載時(shí)并不需要立即加載內(nèi)部類,內(nèi)部類不被加載則不去初始化instance廉嚼,故而不占內(nèi)存玫镐。即當(dāng)SingleTon第一次被加載時(shí),并不需要去加載SingletonHolder前鹅,只有當(dāng)getInstance()方法第一次被調(diào)用時(shí)摘悴,才會(huì)去初始化instance,第一次調(diào)用getInstance()方法會(huì)導(dǎo)致虛擬機(jī)加載SingletonHolder類,這種方法不僅能確保線程安全舰绘,也能保證單例的唯一性蹂喻,同時(shí)也延遲了單例的實(shí)例化。由于不用同步鎖機(jī)制捂寿,性能也會(huì)有所提升口四。

那靜態(tài)內(nèi)部類又是如何實(shí)現(xiàn)線程安全的呢?我們先了解下類的加載時(shí)機(jī)秦陋。JAVA虛擬機(jī)在有且僅有的5種場(chǎng)景下會(huì)對(duì)類進(jìn)行初始化:

  1. new一個(gè)關(guān)鍵字或者一個(gè)實(shí)例化對(duì)象蔓彩;
    讀取或設(shè)置一個(gè)靜態(tài)字段時(shí)(final修飾,已在編譯期把結(jié)果放入常量池的除外)驳概;
    調(diào)用一個(gè)類的靜態(tài)方法時(shí)赤嚼;
  2. 對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)進(jìn)行初始化顺又,需要先調(diào)用其初始化方法進(jìn)行初始化更卒;
  3. 初始化一個(gè)類時(shí),如果其父類還未進(jìn)行初始化稚照,會(huì)先觸發(fā)其父類的初始化蹂空;
  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí)俯萌,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會(huì)先初始化這個(gè)類上枕;
  5. 當(dāng)使用動(dòng)態(tài)語(yǔ)言支持時(shí)咐熙,如果一個(gè)MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic辨萍、REF_invokeStatic的方法句柄棋恼,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化分瘦;

這5種情況被稱為是類的主動(dòng)引用蘸泻,除此之外的所有引用類都不會(huì)對(duì)類進(jìn)行初始化琉苇,稱為被動(dòng)引用嘲玫,靜態(tài)內(nèi)部類就屬于被動(dòng)引用的行列。

我們?cè)倏磄etInstance()方法并扇,取的是SingletonHolder里的instance對(duì)象去团,跟上面那個(gè)DCL方法不同的是,getInstance()方法并沒(méi)有多次去new對(duì)象穷蛹,故不管多少個(gè)線程去調(diào)用getInstance()方法土陪,取的都是同一個(gè)instance對(duì)象,而不用去重新創(chuàng)建肴熏。當(dāng)getInstance()方法被調(diào)用時(shí)鬼雀,SingletonHolder才在SingletonHolder的運(yùn)行時(shí)常量池里,把符號(hào)引用替換為直接引用蛙吏,這時(shí)靜態(tài)對(duì)象instance也真正被創(chuàng)建源哩,然后再被getInstance()方法返回出去。那么instance在創(chuàng)建過(guò)程中又是如何保證線程安全的呢鸦做?

虛擬機(jī)會(huì)保證一個(gè)類的clinit方法在多線程環(huán)境中被正確地加鎖励烦、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類泼诱,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的clinit方法坛掠,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行clinit方法完畢治筒。如果在一個(gè)類的clinit方法中有耗時(shí)很長(zhǎng)的操作屉栓,就可能造成多個(gè)進(jìn)程阻塞(需要注意的是,其他線程雖然會(huì)被阻塞耸袜,但如果執(zhí)行clinit方法后友多,其他線程喚醒之后不會(huì)再次進(jìn)入clinit方法。同一個(gè)加載器下句灌,一個(gè)類型只會(huì)初始化一次)夷陋,在實(shí)際應(yīng)用中欠拾,這種阻塞往往是很隱蔽的。

故而骗绕,可以看出INSTANCE在創(chuàng)建過(guò)程中是線程安全的藐窄,所以說(shuō)靜態(tài)內(nèi)部類形式的單例可保證線程安全,也能保證單例的唯一性酬土,同時(shí)也延遲了單例的實(shí)例化荆忍。

靜態(tài)內(nèi)部類實(shí)現(xiàn)的缺點(diǎn):
傳遞參數(shù)問(wèn)題,由于是靜態(tài)內(nèi)部類的形式去創(chuàng)建單例的撤缴,故外部無(wú)法傳遞參數(shù)進(jìn)去刹枉,所以,我們創(chuàng)建單例時(shí)屈呕,可以在靜態(tài)內(nèi)部類與DCL模式下權(quán)衡微宝。

最后總結(jié):

以上三種實(shí)現(xiàn)方式都是可以達(dá)到線程安全的效果,我們推薦使用靜態(tài)內(nèi)部類的方式虎眨,另外還有枚舉也可以實(shí)現(xiàn)單例蟋软,它不僅能避免多線程同步問(wèn)題,而且還能防止反射或反序列化重新創(chuàng)建新的對(duì)象嗽桩,但是安卓官方不推薦岳守,因?yàn)閮?nèi)存消耗是靜態(tài)常量的兩倍。

public enum  EnumMode {
    INSTANCE;
}

上述三種實(shí)現(xiàn)方式都需要考慮到反序列化的問(wèn)題碌冶,如果Singleton實(shí)現(xiàn)了Serializable接口湿痢,那么這個(gè)類的實(shí)例就可能被序列化和復(fù)原。不管怎樣扑庞,如果序列化一個(gè)單例類的對(duì)象譬重,那么復(fù)原多個(gè)之后就會(huì)有多個(gè)單例類的實(shí)例,因?yàn)樾枰貙憆eadResolve()方法嫩挤,在該方法里返回instance害幅,防止反序列化得到多個(gè)對(duì)象。

  private Object readResolve(){
        return instance;
    }

結(jié)尾:

本周給大家簡(jiǎn)單介紹了單例模式的常見三種實(shí)現(xiàn)方式岂昭,其中雙重鎖和靜態(tài)內(nèi)部類兩種方式涉及到了Java虛擬機(jī)創(chuàng)建對(duì)象的過(guò)程以现,這些內(nèi)容還需要深入了解下。感謝大家的閱讀约啊,我們下周再見邑遏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恰矩,隨后出現(xiàn)的幾起案子记盒,更是在濱河造成了極大的恐慌,老刑警劉巖外傅,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纪吮,死亡現(xiàn)場(chǎng)離奇詭異俩檬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)碾盟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門棚辽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人冰肴,你說(shuō)我怎么就攤上這事屈藐。” “怎么了熙尉?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵联逻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我检痰,道長(zhǎng)包归,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任攀细,我火速辦了婚禮箫踩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谭贪。我一直安慰自己,他們只是感情好锦担,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布俭识。 她就那樣靜靜地躺著,像睡著了一般洞渔。 火紅的嫁衣襯著肌膚如雪套媚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天磁椒,我揣著相機(jī)與錄音堤瘤,去河邊找鬼。 笑死浆熔,一個(gè)胖子當(dāng)著我的面吹牛本辐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播医增,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼慎皱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了叶骨?” 一聲冷哼從身側(cè)響起茫多,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忽刽,沒(méi)想到半個(gè)月后天揖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夺欲,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年今膊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洁闰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡万细,死狀恐怖扑眉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赖钞,我是刑警寧澤腰素,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站雪营,受9級(jí)特大地震影響弓千,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜献起,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一洋访、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谴餐,春花似錦姻政、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至厌殉,卻和暖如春食绿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背公罕。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工器紧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人楼眷。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓铲汪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親摩桶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桥状,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容