單例模式的幾種寫法和對(duì)比

1.餓漢
private static Singleton instance=new Singleton();

private static Singleton instance=null;
static {
instance=new Singleton();
}
其實(shí)就是在類加載時(shí)胶台,準(zhǔn)備階段里把類的屬性實(shí)例化,缺點(diǎn)是可能提前加載到內(nèi)存。
2.線程不安全的懶漢
private static Singleton instance瘫拣;
其實(shí)就是沒有任何鎖甚脉,用的時(shí)候再去判斷并初始化兜粘,有可能在判斷為空和執(zhí)行初始化期間构挤,別的線程完成了初始化
3.線程安全的懶漢
private static Singleton instance散吵;
public synchronized Singleton getInstance(){}
其實(shí)就是為靜態(tài)方法加了類鎖耽装,凡是用這個(gè)靜態(tài)函數(shù)的愤炸,一律加鎖,缺點(diǎn)就是鎖阻塞掉奄,影響效率
4.雙重校驗(yàn)鎖
private volatile static Singleton instance;
private void Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
}
用synchronized來實(shí)現(xiàn)鎖同步的實(shí)例化规个,因?yàn)楂@取鎖之后會(huì)更新一次數(shù)據(jù),所以要再檢查一次instance是否為空
注意,使用volatile主要是為了實(shí)現(xiàn)有序性绰姻,instance=new Singleton();實(shí)際上是3個(gè)指令(分配內(nèi)存+實(shí)例化+指向引用)枉侧,有可能遇到指令重排序?qū)е鲁鲥e(cuò)的問題,volatile會(huì)通過內(nèi)存屏障狂芋,禁止重排序榨馁,順便可以用來實(shí)現(xiàn)可見性,減少資源消耗帜矾。
5.靜態(tài)內(nèi)部類
private static class SingletonHolder{
private static final Singleton instance=new Singlton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
private void Singlton(){}
其實(shí)就是利用類的主動(dòng)加載機(jī)制翼虫,只有在使用到靜態(tài)類的對(duì)象時(shí),才去加載類屡萤,這樣靜態(tài)內(nèi)部類一定是在getInstance時(shí)加載珍剑。
另外,靜態(tài)對(duì)象雖然天然只有方法區(qū)的一個(gè)實(shí)例死陆,但也有被修改的可能招拙,所以用final確保線程安全。
靜態(tài)內(nèi)部類相比內(nèi)部類來說措译,不能直接調(diào)用宿主類的函數(shù)别凤,但是也擁有內(nèi)部類的訪問權(quán)限,所以不受private限制
6.枚舉
public enum EnumSingleton{
INSTANCE;
private Singleton instance;
private EnumSingleton(){
instance=new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
其實(shí)就是因?yàn)槊杜e類型是static final類型领虹,而且只能實(shí)例化一次规哪,原理上其實(shí)和靜態(tài)內(nèi)部類的寫法一樣。
單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法塌衰。
多個(gè)classloader破壞單例
不同classloader加載的類不是同一個(gè)類诉稍,所以要避免多個(gè)classloader加載出多個(gè)單例對(duì)象。
這種情況下最疆,可以考慮用當(dāng)前線程的ClassLoader來加載類杯巨。
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
反射破壞單例
因?yàn)榉瓷淇梢阅玫絚onstructor構(gòu)造函數(shù),所以調(diào)用構(gòu)造函數(shù)的newInstance方法能破壞單例:

//獲得構(gòu)造器
Constructor con = Singleton.class.getDeclaredConstructor();
//設(shè)置為可訪問
con.setAccessible(true);
//構(gòu)造新的對(duì)象
Singleton singleton1 = (Singleton)con.newInstance();

對(duì)于需要構(gòu)造函數(shù)的單例寫法來說努酸,因?yàn)橹皇窃谡G闆r下屏蔽了構(gòu)造函數(shù)舔箭,所以可以用反射訪問到構(gòu)造函數(shù),破壞單例蚊逢;
只有枚舉單例不能被反射破壞,因?yàn)槊杜e單例在編譯后箫章,其實(shí)是abstract類烙荷,無法初始化,而且Java在newInstanc時(shí)對(duì)Enum禁止檬寂。
反序列化破壞單例
如果單例是可序列化的终抽,就很容易通過反序列化得到多個(gè)單例對(duì)象。因?yàn)镺bjectInputStream的readResolve函數(shù)會(huì)反射執(zhí)行構(gòu)造函數(shù),繞過單例的代碼限制機(jī)制昼伴。
注意匾旭,枚舉單例不能implement Serializable,否則readObject的特性“每次都返回新建的實(shí)例”會(huì)破壞單例圃郊。
這種情況下价涝,可以考慮修改readResolve函數(shù),返回餓漢的靜態(tài)單例對(duì)象持舆。
public static Singleton INSTANCE = new Singleton();
private Object readResolve() {
return INSTANCE;
}
構(gòu)造函數(shù)報(bào)錯(cuò)
根據(jù)Initialization-on-demand holder idiom的提示色瘩,使用內(nèi)部靜態(tài)類這種單利寫法時(shí),需要確保構(gòu)造函數(shù)不拋異常逸寓,例如:

    private SingletonClass() {
        throw new RuntimeException("exception in constructor");
    }

文章聲稱居兆,這種情況會(huì)導(dǎo)致調(diào)用報(bào)錯(cuò),NoClassDefFoundError竹伸。
不過泥栖,當(dāng)我們實(shí)際測(cè)試一下兩次調(diào)用,會(huì)發(fā)現(xiàn)兩次報(bào)錯(cuò)不一樣:
第一次報(bào)錯(cuò)其實(shí)是ExceptionInInitializerError勋篓,這是因?yàn)榈谝淮握{(diào)用SingletonClass.getInstance()時(shí)吧享,需要加載目標(biāo)類(懶加載),在加載過程中生巡,執(zhí)行構(gòu)造函數(shù)失敗耙蔑,所以報(bào)錯(cuò)會(huì)是ExceptionInInitializerError。
第二次報(bào)錯(cuò)因?yàn)榈谝淮渭虞d類失敗孤荣,類只會(huì)加載一次甸陌,而第一次的加載過程失敗了,所以會(huì)報(bào)NoClassDefFoundError盐股,找不到目標(biāo)類钱豁。

單例模式的七種寫法
為什么要用枚舉實(shí)現(xiàn)單例模式(避免反射、序列化問題)
Java transient關(guān)鍵字使用小記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疯汁,一起剝皮案震驚了整個(gè)濱河市牲尺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幌蚊,老刑警劉巖谤碳,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溢豆,居然都是意外死亡蜒简,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門漩仙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搓茬,“玉大人犹赖,你說我怎么就攤上這事【砺兀” “怎么了峻村?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)锡凝。 經(jīng)常有香客問我粘昨,道長(zhǎng),這世上最難降的妖魔是什么私爷? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任雾棺,我火速辦了婚禮,結(jié)果婚禮上衬浑,老公的妹妹穿的比我還像新娘捌浩。我一直安慰自己,他們只是感情好工秩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布尸饺。 她就那樣靜靜地躺著,像睡著了一般助币。 火紅的嫁衣襯著肌膚如雪浪听。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天眉菱,我揣著相機(jī)與錄音迹栓,去河邊找鬼。 笑死俭缓,一個(gè)胖子當(dāng)著我的面吹牛克伊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播华坦,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼愿吹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了惜姐?” 一聲冷哼從身側(cè)響起犁跪,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歹袁,沒想到半個(gè)月后坷衍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡条舔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年枫耳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逞刷。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘉涌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夸浅,到底是詐尸還是另有隱情仑最,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布帆喇,位于F島的核電站警医,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坯钦。R本人自食惡果不足惜预皇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婉刀。 院中可真熱鬧吟温,春花似錦、人聲如沸突颊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽律秃。三九已至爬橡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棒动,已是汗流浹背糙申。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留船惨,地道東北人柜裸。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像掷漱,于是被迫代替她去往敵國(guó)和親粘室。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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