Java/Android 5種單例模式

對(duì)幾種單例寫(xiě)法的整理疚俱,并分析其優(yōu)缺點(diǎn)向叉。如何創(chuàng)建一個(gè)線程安全的單例单绑,什么是雙檢鎖,那這篇文章可能會(huì)幫助到你甘畅。

懶漢式 非線程安全

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}

代碼簡(jiǎn)單明了,使用了懶加載模式往弓,但是卻存在問(wèn)題橄浓。當(dāng)有多個(gè)線程并行調(diào)用 getInstance() 的時(shí),就會(huì)創(chuàng)建多個(gè)實(shí)例亮航。在多線程下不能正常工作荸实。

懶漢式,線程安全

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static synchronized Singleton getInstance() {
      if (instance == null) {
         instance = new Singleton();
      }
      return instance;
    }
}

雖做到線程安全缴淋,并且解決了多實(shí)例的問(wèn)題准给,但是并不高效。因?yàn)樵谌魏螘r(shí)候只能有一個(gè)線程調(diào)用 getInstance() 方法重抖。但是同步操作只在第一次調(diào)用時(shí)才被需要露氮,即第一次創(chuàng)建單例實(shí)例時(shí)。雙重檢驗(yàn)鎖就能解決這個(gè)問(wèn)題钟沛。

雙重檢驗(yàn)鎖

雙重檢驗(yàn)鎖畔规,是一種使用同步塊加鎖的方法。又稱其為雙重檢查鎖恨统,因?yàn)闀?huì)有兩次檢查 instance == null叁扫,一次是在同步塊外三妈,一次是在同步塊內(nèi)。為何在同步塊內(nèi)還要再檢驗(yàn)莫绣?因?yàn)榭赡軙?huì)有多個(gè)線程一起進(jìn)入同步塊外的 if畴蒲,如果在同步塊內(nèi)不進(jìn)行二次檢驗(yàn)的話就會(huì)生成多個(gè)實(shí)例。

public class Singleton {
    private volatile static Singleton instance;//聲明volatile对室,原子操作
    private Singleton (){}
    public static Singleton getSingleton() {
       if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 //Double Checked
                instance = new Singleton();
            }
        }
      }
      return instance;
    }
}
  1. 給 instance 分配內(nèi)存
  2. 調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量
  3. 將instance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)

在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化模燥。上面的2和3的順序是不能保證的,最終執(zhí)行順序有可能是 1-2-3 也可能是 1-3-2掩宜。如果是后者蔫骂,則在 3 執(zhí)行完畢、2 未執(zhí)行之前牺汤,被其他線程搶占纠吴,這時(shí) instance 已經(jīng)是非 null 了(但卻沒(méi)有初始化),所以線程二會(huì)直接返回 instance慧瘤,然后使用戴已,最后報(bào)錯(cuò),所以才要使用volatile

volatile

  • 禁止指令重排序優(yōu)化锅减。也就是說(shuō)糖儡,在 volatile 變量的賦值操作后面會(huì)有一個(gè)內(nèi)存屏障(生成的匯編代碼上),讀操作不會(huì)被重排序到內(nèi)存屏障之前怔匣。比如上面的例子握联,取操作必須在執(zhí)行完 1-2-3 之后或者 1-3-2 之后,不存在執(zhí)行到 1-3 然后取到值的情況每瞒。從「先行發(fā)生原則」的角度理解的話金闽,就是對(duì)于一個(gè) volatile 變量的寫(xiě)操作都先行發(fā)生于后面對(duì)這個(gè)變量的讀操作(這里的“后面”是時(shí)間上的先后順序)。
  • 特別注意在 Java 5 以前的版本使用 volatile 的雙檢鎖還是有問(wèn)題的剿骨。其原因是 Java 5 以前的 JMM (Java 內(nèi)存模型)是存在缺陷的代芜,即時(shí)將變量聲明成 volatile 也不能完全避免重排序,主要是 volatile 變量前后的代碼仍然存在重排序問(wèn)題浓利。這個(gè) volatile 屏蔽重排序的問(wèn)題在 Java 5 中才得以修復(fù)挤庇,所以在這之后才可以放心使用 volatile。

餓漢式 static final field

單例的實(shí)例被聲明成 static 和 final 變量贷掖,開(kāi)始就加載類到內(nèi)存中時(shí)就會(huì)初始化嫡秕,所以創(chuàng)建實(shí)例本身是線程安全的。

public class Singleton{
   //類加載時(shí)就初始化
   private static final Singleton instance = new Singleton();
   private Singleton(){}
   public static Singleton getInstance(){
     return instance;
   }
}
  • 缺點(diǎn):不是一種懶加載模式
  • 單例會(huì)在加載類后一開(kāi)始就被初始化苹威,即使客戶端沒(méi)有調(diào)用 getInstance()方法昆咽。
  • 餓漢式的創(chuàng)建方式在某些場(chǎng)景中無(wú)法使用:如 Singleton 實(shí)例的創(chuàng)建是依賴參數(shù)或配置文件的,在 getInstance() 之前必須調(diào)用某個(gè)方法設(shè)置參數(shù)給它掷酗,這種單例就無(wú)法使用了调违。

靜態(tài)內(nèi)部類 static nested class

該方法也是《Effective Java》上推薦的汇在。

public class Singleton { 
   private static class SingletonHolder { 
     private static final Singleton INSTANCE = new Singleton(); 
   } 
   private Singleton (){} 
   public static final Singleton getInstance() { 
     return SingletonHolder.INSTANCE; 
   } 
}
  • 使用JVM本身機(jī)制保證了線程安全問(wèn)題翰萨;
  • 由于 SingletonHolder 是私有的糕殉,除了 getInstance() 外沒(méi)有辦法訪問(wèn)它,懶漢式殖告;
  • 同時(shí)讀取實(shí)例的時(shí)候不會(huì)進(jìn)行同步阿蝶,沒(méi)有性能缺陷;
  • 不依賴 JDK 版本羡洁。

枚舉 Enum

用枚舉寫(xiě)單例太簡(jiǎn)單

public enum Singleton {
   INSTANCE;
}

可通過(guò)Singleton.INSTANCE來(lái)訪問(wèn)實(shí)例,這比調(diào)用getInstance()方法簡(jiǎn)單多了筑煮。創(chuàng)建枚舉默認(rèn)就是線程安全的,還能防止反序列化導(dǎo)致重新創(chuàng)建新的對(duì)象粤蝎。但還是很少看到有人這樣寫(xiě)真仲。

總結(jié)

一般來(lái)說(shuō),單例模式有五種寫(xiě)法:懶漢初澎、餓漢秸应、雙重檢驗(yàn)鎖碑宴、靜態(tài)內(nèi)部類软啼、枚舉延柠。上述所說(shuō)都是線程安全的實(shí)現(xiàn)

  • 一般使用餓漢式
  • 要求懶加載傾向靜態(tài)內(nèi)部類
  • 反序列化創(chuàng)建對(duì)象用枚舉。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贞间,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榜跌,更是在濱河造成了極大的恐慌,老刑警劉巖钓葫,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帆调,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門含鳞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人芹务,你說(shuō)我怎么就攤上這事≡姹В” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵桅狠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我轿秧,道長(zhǎng),這世上最難降的妖魔是什么菇篡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮陨仅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灼伤。我一直安慰自己,他們只是感情好咪鲜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著颖侄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪览祖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天展蒂,我揣著相機(jī)與錄音又活,去河邊找鬼锰悼。 笑死柳骄,一個(gè)胖子當(dāng)著我的面吹牛箕般,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丝里,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丙者!你這毒婦竟也來(lái)了营密?” 一聲冷哼從身側(cè)響起械媒,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤评汰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后被去,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惨缆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坯墨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捣染,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耍攘,到底是詐尸還是另有隱情,我是刑警寧澤蕾各,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站式曲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜始鱼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望医清。 院中可真熱鬧,春花似錦会烙、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)五嫂。三九已至,卻和暖如春沃缘,著一層夾襖步出監(jiān)牢的瞬間躯枢,已是汗流浹背槐臀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留水慨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓晰洒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親欢顷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子槽棍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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