Java單例模式詳解

  • 目錄
    • 一.什么是單例吗购?
    • 二.有幾種医男?
    • 三.應(yīng)用場(chǎng)景
    • 四.注意的地方

一.什么是單例?

單例模式 保證一個(gè)類在內(nèi)存中只有一個(gè)實(shí)例(對(duì)象)捻勉,并提供一個(gè)訪問它的全局訪問點(diǎn)镀梭。

通常我們可以讓一個(gè)全局變量使得一個(gè)對(duì)象被訪問,但它不能防止你實(shí)例化多個(gè)對(duì)象踱启。一個(gè)最好的辦法就是报账,讓類自身負(fù)責(zé)保存它的唯一實(shí)例。這個(gè)類可以保證沒有其他實(shí)例可以被創(chuàng)建埠偿,并且它可以提供一個(gè)訪問該實(shí)例的方法 —— 大話設(shè)計(jì)模式--第21章

單例Boy.jpg

二.單例有幾種透罢?

具體區(qū)分為下面幾種:

  • 餓漢式
  • 懶(飽)漢式
    • 線程不安全(單線程下操作的)
    • 線程安全
      • 方法加鎖式
      • 雙重判斷加鎖式DCL(Double-Check Locking)--- 方法加鎖式加強(qiáng)版
  • 靜態(tài)嵌套類式(推薦使用)
  • 枚舉式(推薦使用)
  • 使用容器單例

不和你多bb ,上代碼

b.jpg

1.餓漢式

public class Signleton{
    //對(duì)于一個(gè)final變量冠蒋。  
    // 如果是基本數(shù)據(jù)類型的變量羽圃,則其數(shù)值一旦在初始化之后便不能更改;  
    // 如果是引用類型的變量抖剿,則在對(duì)其初始化之后便不能再讓其指向另一個(gè)對(duì)象朽寞。
   private final static Signleton instance = new Signleton();
   
   private Signleton(){
       
   }
   
   public static  Signleton getInstance(){
        return instance;
   }
}

解釋: 拿時(shí)間換空間识窿,因?yàn)閷?shí)例對(duì)象在類加載過程中就會(huì)被創(chuàng)建,在getInstance()方法中只是直接返回對(duì)象引用脑融。因?yàn)檫@種創(chuàng)建實(shí)例對(duì)象方式比較‘急’喻频,所以稱為餓漢式。

優(yōu)點(diǎn):快很準(zhǔn)肘迎,無需關(guān)心線程安全問題半抱。

缺點(diǎn)
??1.無論對(duì)象會(huì)不會(huì)被使用,在類加載的時(shí)候就創(chuàng)建對(duì)象了膜宋,這樣會(huì)降低內(nèi)存的使用率窿侈。
?? 2.如果在一個(gè)大環(huán)境下使用了過多的餓漢單例,則會(huì)生產(chǎn)出過多的實(shí)例對(duì)象秋茫,無論你是否要使用他們

餓.jpg

2.懶(飽)漢式

(1)線程不安全(單線程下操作的)

public class Signleton{
  private static Signleton instance = null;
  
  private Signleton(){
  
  }
  
  public static Signleton getInstance(){
     if(instance==null){
        instance = new Signleton();
     }
     return instance;
  }
}

解釋: Singleton的靜態(tài)屬性instance中史简,只有instance為null的時(shí)候才創(chuàng)建一個(gè)實(shí)例,構(gòu)造函數(shù)私有肛著,確保每次都只創(chuàng)建一個(gè)圆兵,避免重復(fù)創(chuàng)建。之所以被稱為“懶漢”枢贿,因?yàn)樗軕醒撑患敝a(chǎn)實(shí)例,在需要的時(shí)候才會(huì)生產(chǎn)局荚。

優(yōu)點(diǎn):延遲加載

缺點(diǎn):只在單線程的情況下正常運(yùn)行超凳,在多線程的情況下,就會(huì)出問題耀态。例如:當(dāng)兩個(gè)線程同時(shí)運(yùn)行到判斷instance是否為空的if語句轮傍,并且instance確實(shí)沒有創(chuàng)建好時(shí),那么兩個(gè)線程都會(huì)創(chuàng)建一個(gè)實(shí)例首装。

(2)線程安全

方法加鎖式
public class Signleton{
  private static Signleton instance = null;
  
  private Signleton(){
  
  }
  //給方法加鎖 若有ABCD線程使用 A線程先進(jìn)入 BCD線程都需要等待A線程執(zhí)行完畢釋放鎖才能獲得鎖執(zhí)行該方法
  //這樣效率較低 
  public syschronized static Signleton getInstance(){
     if(instance==null){
        instance = new Signleton();
     }
     return instance;
  }
}

解釋:給方法添加[synchronized],使之成為同步函數(shù)创夜。兩個(gè)線程同時(shí)想創(chuàng)建實(shí)例,由于在一個(gè)時(shí)刻只有一個(gè)線程能得到同步鎖仙逻,當(dāng)?shù)谝粋€(gè)線程加上鎖以后驰吓,第二個(gè)線程只能等待。第一個(gè)線程發(fā)現(xiàn)實(shí)例沒有創(chuàng)建系奉,創(chuàng)建之檬贰。第一個(gè)線程釋放同步鎖,第二個(gè)線程才可以加上同步鎖喜最,執(zhí)行下面的代碼偎蘸。由于第一個(gè)線程已經(jīng)創(chuàng)建了實(shí)例,所以第二個(gè)線程不需要?jiǎng)?chuàng)建實(shí)例。保證在多線程的環(huán)境下也只有一個(gè)實(shí)例迷雪。

優(yōu)點(diǎn): 保證了線程安全限书。延時(shí)加載,用的時(shí)候才會(huì)生產(chǎn)對(duì)象章咧。

缺點(diǎn): 需要保證同步倦西,付出效率的代價(jià)。加鎖是很耗時(shí)的赁严。扰柠。。

雙重判斷加鎖式DCL
public class Signleton {
  private static Signleton instance = null;
  
  private Signleton(){
  
  }

  //假設(shè)有ABCD 個(gè)線程 使用這個(gè)方法
  public static Signleton getInstance(){
  //BCD都進(jìn)入了這個(gè)方法
    if(instance==null){
      //而A線程已經(jīng)給第二個(gè)的判斷加鎖了 
      syschronized(Signleton.class){
         //這時(shí)A掛起,對(duì)象instance還沒創(chuàng)建 疼约,故BCD都進(jìn)入了第一個(gè)判斷里面卤档,并排隊(duì)等待A釋放鎖
         //A喚醒繼續(xù)執(zhí)行并創(chuàng)建了instance對(duì)象,執(zhí)行完畢釋放鎖程剥。
         //此時(shí)到B線程進(jìn)入到第二個(gè)判斷并加鎖劝枣,但由于B進(jìn)入第二個(gè)判斷時(shí)instance 不為null了  故需要再判斷多一次  不然會(huì)再創(chuàng)建一次實(shí)例
          if(instance==null){
             instance = new Signleton();
          }
       
      } 
    }
    return instance;
   }
}

解釋:方法加鎖式的優(yōu)化。只有當(dāng)instance為null時(shí)织鲸,需要獲取同步鎖舔腾,創(chuàng)建一次實(shí)例。當(dāng)實(shí)例被創(chuàng)建搂擦,則無需試圖加鎖稳诚。。

優(yōu)點(diǎn): 保證了線程安全瀑踢。延時(shí)加載扳还,用的時(shí)候才會(huì)生產(chǎn)對(duì)象。進(jìn)行雙重判斷丘损,當(dāng)已經(jīng)創(chuàng)建過實(shí)例對(duì)象后就無需加鎖普办,提高效率工扎。

缺點(diǎn): 編寫復(fù)雜徘钥、難記憶。雖然是優(yōu)化加鎖式肢娘,但加鎖始終會(huì)耗時(shí)呈础。

3.靜態(tài)嵌套類式(推薦使用)
public class Signleton{
   private Signleton{
   }
  
   //靜態(tài)嵌套類  這里給個(gè)鏈接 區(qū)分靜態(tài)嵌套類和內(nèi)部類[靜態(tài)嵌套類和內(nèi)部類](http://blog.csdn.net/iispring/article/details/46490319)
   private static class  SignletonHolder{
      public static final Signleton instance = new Signleton();
  }
  
  public static Signleton getInstance(){
   return SignletonHolder.instance;
ins't
  }
  
}

解釋: 定義一個(gè)私有的靜態(tài)嵌套類,在第一次用這個(gè)嵌套類時(shí)橱健,會(huì)創(chuàng)建一個(gè)實(shí)例而钞。而類型為SingletonHolder的類,只有在Singleton.getInstance()中調(diào)用拘荡,由于私有的屬性臼节,他人無法使用SingleHolder,不調(diào)用Singleton.getInstance()就不會(huì)創(chuàng)建實(shí)例。

優(yōu)點(diǎn): 保證了線程安全网缝。無需加鎖巨税,延遲加載,第一次調(diào)用Singleton.getInstance才創(chuàng)建實(shí)例粉臊。推薦使用草添。

缺點(diǎn): 無。

4.枚舉式(推薦使用)

為了方便理解枚舉式 這邊簡(jiǎn)單介紹一下枚舉(在jdk1.5后)

//枚舉Type
public enum Type{
   A,B,C;
  private String type;
  Type(type){
     this.type = type;
 }
 public String getType(){
    return type;
  }
}
//可認(rèn)為等于下面的
public class Type{
  public static final Type A=new Type(A);
  public static final Type B=new Type(B);
  public static final Type C=new Type(C);
ins't
}

所以Type.A.getType()為A.
推薦去了解一下
Java學(xué)習(xí)整理系列之Java枚舉類型的使用
Java學(xué)習(xí)整理系列之Java枚舉類型的原理

好了 開始介紹枚舉式了 看代碼

public class Signleton{

public static Signleton getInstance(){
   return SignletonEnum.INSTANCE.getInstance();
}

public enum SignletonEnum{
   INSTANCE;
   
   private Signleton instance;
   
   //由于JVM只會(huì)初始化一次枚舉實(shí)例扼仲,所以instance無需加static 
   private SignletonEnum(){
        instance = new Signleton();
   }
   
   public getInstance(){
       return instance;   
   }
}

}

解釋: 定義內(nèi)部的枚舉远寸,由于類加載時(shí)JVM只會(huì)初始化一次枚舉實(shí)例,所以在構(gòu)造函數(shù)中創(chuàng)建Signgleton對(duì)象并保證了這個(gè)對(duì)象實(shí)例唯一屠凶。
通過調(diào)用枚舉INSTANCE方法getInstance (SignletonEnum.INSTANCE.getInstance())獲取實(shí)例對(duì)象社搅。

優(yōu)點(diǎn): 枚舉提供了序列化機(jī)制--例如在我們要通過網(wǎng)絡(luò)傳輸一個(gè)數(shù)據(jù)庫(kù)連接的句柄,會(huì)提供很多幫助磅崭。

單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法筋帖。Effective Java

5.使用容器單例
public class Singleton { 
   //用Map保存該單例
    private static Map<String, Object> objMap = new HashMap<>(); 

    private Singleton() { 

    } 

    public static void putObject(String key, String instance){ 
        if(!objMap.containsKey(key)){ 
            objMap.put(key, instance); 
        } 
    } 

    public static Object getObject(String key){ 
        return objMap.get(key); 
    } 
}

解釋: 在程序開始的時(shí)候?qū)卫愋妥⑷氲揭粋€(gè)容器之中 ,在使用的時(shí)候再根據(jù) key 值獲取對(duì)應(yīng)的實(shí)例,這種方式可以使我們很方便的管理很多單例對(duì)象,也對(duì)用戶隱藏了具體實(shí)現(xiàn)類,降低了耦合度。

缺點(diǎn): 會(huì)造成內(nèi)存泄漏贱枣。(所以我們一般在生命周期銷毀的時(shí)候也要去銷毀它) 监署。

三.應(yīng)用場(chǎng)景

一般創(chuàng)建一個(gè)對(duì)象需要消耗過多的資源,如:訪問I0和數(shù)據(jù)庫(kù)等資源或者有很多個(gè)地方都用到了這個(gè)實(shí)例。

四.注意的地方

雙重判斷加鎖式DCL :這種寫法也并不是保證完全100%的可靠,由于 java 編譯器允許執(zhí)行無序,并且 jdk1.5之前的jvm ( java 內(nèi)存模型)中的 Cache,寄存器到主內(nèi)存的回寫順序規(guī)定,第二個(gè)和第三個(gè)執(zhí)行是無法保證按順序執(zhí)行的,也就是說有可能1-2-3也有可能是1-3-2; 這時(shí)假如有 A 和 B 兩條線程, A線程執(zhí)行到3的步驟,但是未執(zhí)行2,這時(shí)候 B 線程來了搶了權(quán)限,直接取走 instance 這時(shí)候就有可能報(bào)錯(cuò)纽哥。

簡(jiǎn)單總結(jié)就是說jdk1.5之前會(huì)造成兩個(gè)問題:

1钠乏、線程間共享變量不可見性;

2、無序性(執(zhí)行順序無法保證);

當(dāng)然這個(gè)bug已經(jīng)修復(fù)了,SUN官方調(diào)整了JVM,具體了Volatile關(guān)鍵字,因此在jdk1.5之前只需要寫成這樣既可, private Volatitle static Singleton instance; 這樣就可以保證每次都是從主內(nèi)存中取,當(dāng)然這樣寫或多或少的回影響性能,但是為了安全起見,這點(diǎn)性能犧牲還是值得春塌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晓避,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子只壳,更是在濱河造成了極大的恐慌俏拱,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吼句,死亡現(xiàn)場(chǎng)離奇詭異锅必,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惕艳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門搞隐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人远搪,你說我怎么就攤上這事劣纲。” “怎么了谁鳍?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵癞季,是天一觀的道長(zhǎng)劫瞳。 經(jīng)常有香客問我,道長(zhǎng)绷柒,這世上最難降的妖魔是什么柠新? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮辉巡,結(jié)果婚禮上恨憎,老公的妹妹穿的比我還像新娘。我一直安慰自己郊楣,他們只是感情好憔恳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著净蚤,像睡著了一般钥组。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上今瀑,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天程梦,我揣著相機(jī)與錄音,去河邊找鬼橘荠。 笑死屿附,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哥童。 我是一名探鬼主播挺份,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼贮懈!你這毒婦竟也來了匀泊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤朵你,失蹤者是張志新(化名)和其女友劉穎各聘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抡医,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躲因,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魂拦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毛仪。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖芯勘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腺逛,我是刑警寧澤荷愕,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響安疗,放射性物質(zhì)發(fā)生泄漏抛杨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一荐类、第九天 我趴在偏房一處隱蔽的房頂上張望怖现。 院中可真熱鬧,春花似錦玉罐、人聲如沸屈嗤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饶号。三九已至,卻和暖如春季蚂,著一層夾襖步出監(jiān)牢的瞬間茫船,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工扭屁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留算谈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓料滥,卻偏偏與公主長(zhǎng)得像濒生,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幔欧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 1 場(chǎng)景問題# 1.1 讀取配置文件的內(nèi)容## 考慮這樣一個(gè)應(yīng)用罪治,讀取配置文件的內(nèi)容。 很多應(yīng)用項(xiàng)目礁蔗,都有與應(yīng)用相...
    七寸知架構(gòu)閱讀 6,648評(píng)論 12 68
  • 前言 本文主要參考 那些年觉义,我們一起寫過的“單例模式”。 何為單例模式浴井? 顧名思義晒骇,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,485評(píng)論 1 8
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單、最易理解的設(shè)計(jì)模式磺浙,也因?yàn)樗暮?jiǎn)潔易懂洪囤,是項(xiàng)目中最...
    成熱了閱讀 4,226評(píng)論 4 34
  • 單例模式(Singleton Pattern)是眾多設(shè)計(jì)模式中較為簡(jiǎn)單的一個(gè),同時(shí)它也是面試時(shí)經(jīng)常被提及的問題撕氧,如...
    廖少少閱讀 554評(píng)論 0 1
  • 》昨晚喝了滿滿一湖的西湖水《 雨前悶熱的空氣 穹罩大地 如一空明不透的玻璃鏡 要怎樣 打破窒息般的結(jié)界 出門撒歡釋...
    崖邊草閱讀 221評(píng)論 0 0