創(chuàng)建型模式之單例模式

創(chuàng)建型模式:對象實例化的模式氛赐,創(chuàng)建型模式用于解耦對象的實例化過程述么,即創(chuàng)建對象的同時隱藏創(chuàng)建邏輯。

1、使用場景

什么是單例模式呢贷祈,單例模式(Singleton)又叫單態(tài)模式趋急,它出現(xiàn)目的是為了保證一個類在系統(tǒng)中只有一個實例,并提供一個訪問它的全局訪問點势誊。從這點可以看出呜达,單例模式的出現(xiàn)是為了可以保證系統(tǒng)中一個類只有一個實例而且該實例又易于外界訪問,從而方便對實例個數(shù)的控制并節(jié)約系統(tǒng)資源而出現(xiàn)的解決方案键科。

使用單例模式當然是有原因闻丑,有好處的了。在下面幾個場景中適合使用單例模式:

1勋颖、有頻繁實例化然后銷毀的情況,也就是頻繁的 new 對象勋锤,可以考慮單例模式饭玲;
2、創(chuàng)建對象時耗時過多或者耗資源過多叁执,但又經(jīng)常用到的對象茄厘;
3、頻繁訪問 IO 資源的對象谈宛,例如數(shù)據(jù)庫連接池或訪問本地文件次哈;
4、有狀態(tài)的工具類對象吆录。

  • 1窑滞、網(wǎng)站的計數(shù)器,通過單例模式可以很好實現(xiàn)

  • 2恢筝、配置文件訪問類
    項目中經(jīng)常需要一些環(huán)境相關(guān)的配置文件哀卫,比如短信通知相關(guān)的、郵件相關(guān)的撬槽。如果不用單例的話此改,每次都要 new 對象,每次都要重新讀一遍配置文件侄柔,很影響性能共啃,如果用單例模式,則只需要讀取一遍就好了暂题。
    如果我們使用Spring框架移剪,可以用 @PropertySource 注解讀取properties文件,默認就是單例模式敢靡。

    public class SingleProperty {
    
      private static Properties prop;
    
      private static class SinglePropertyHolder{
          private static final SingleProperty singleProperty = new SingleProperty();
      }
    
      /**
      * config.properties 內(nèi)容是 test.name=kite 
      */
      private SingleProperty(){
          System.out.println("構(gòu)造函數(shù)執(zhí)行");
          prop = new Properties();
          InputStream stream = SingleProperty.class.getClassLoader()
                  .getResourceAsStream("config.properties");
          try {
              prop.load(new InputStreamReader(stream, "utf-8"));
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
    
      public static SingleProperty getInstance(){
          return SinglePropertyHolder.singleProperty;
      }
      
      
      public String getName(){
          return prop.get("test.name").toString();
      }
    
      public static void main(String[] args){
          SingleProperty singleProperty = SingleProperty.getInstance();
          System.out.println(singleProperty.getName());
      }
    }
    
  • 3挂滓、數(shù)據(jù)庫連接池、線程池
    數(shù)據(jù)庫連接池的實現(xiàn),也包括線程池赶站。為什么要做池化幔虏,是因為新建連接很耗時,如果每次新任務(wù)來了贝椿,都新建連接想括,那對性能的影響實在太大。所以一般的做法是在一個應(yīng)用內(nèi)維護一個連接池烙博,這樣當任務(wù)進來時瑟蜈,如果有空閑連接,可以直接拿來用渣窜,省去了初始化的開銷铺根。用單例模式,正好可以實現(xiàn)一個應(yīng)用內(nèi)只有一個線程池的存在乔宿,所有需要連接的任務(wù)位迂,都要從這個連接池來獲取連接。如果不使用單例详瑞,那么應(yīng)用內(nèi)就會出現(xiàn)多個連接池掂林,那也就沒什么意義了。如果你使用 Spring 的話坝橡,并集成了例如 druid 或者 c3p0 泻帮,這些成熟開源的數(shù)據(jù)庫連接池,一般也都是默認以單例模式實現(xiàn)的计寇。

2锣杂、volatile雙重檢查鎖機制

public class SingleInstance {
    private static volatile SingleInstance singleInstance;
    private Singleton(){};
    public static SingleInstance getSingleInstance(){ //1
        //非空則跳過,因為只有首次初始化才有安全問題,保證了初始化之后饲常,線程不會阻塞蹲堂,提高了性能
        if (singleInstance == null) { //2. 以一次檢查
            synchronized(SingleInstance.class){ //3 加鎖
                //voliatile能保證可見性,但不能保證原子性贝淤,加鎖保證線程并發(fā)情況下柒竞,也只有一個實例
                if (singleInstance == null) { //4 第二次檢查
                    singleInstance = new SingleInstance();//5 創(chuàng)建實例 
                }
            }
        }
        return singleInstance;
    }
}

注:原子性,簡單的說播聪,就是多線程下朽基,簡單的賦值和讀取操作,具體請參考Java內(nèi)存模型

優(yōu)缺點:

  • 雙重檢查鎖機制离陶,兼顧性能與安全稼虎,初始化之后,不會發(fā)生線程阻塞
    這是單例模式優(yōu)先推薦的寫法

Java指令執(zhí)行的過程:

  • 1.將變量從主存復(fù)制到線程的工作內(nèi)存中招刨;
  • 2.然后進行讀操作霎俩;
  • 3.有賦值指令時進行賦值操作;
  • 4.將結(jié)果寫入主存中;
    以上4步都是原子性的打却,但組合到一起杉适,多線程操作時不能保證整體原子性,這也就是線程并發(fā)安全問題的原因柳击。

volatile修飾詞作用:

  • 1.某一線程對volatile修飾的變量進行修改后猿推,會強制將結(jié)果寫入主存,并使其它線程緩存行失效(失效后捌肴,讀操作不能從工作內(nèi)存中直接讀取蹬叭,從步驟1開始),即保證3和4指令執(zhí)行過程的整體原子性状知,并通知其它線程秽五。
  • 2.禁止指令重排(代碼的編寫順序和指令執(zhí)行的順序不一致),一定程度上保證了有序性饥悴。

上述的singleInstance類變量假如沒有用volatile關(guān)鍵字修飾的筝蚕,則會導致這樣一個問題:
多線程情況下,在B線程執(zhí)行第5步時铺坞,A線程執(zhí)行到第4步,由于重排序的原因洲胖,B線程還沒有完成instance引用的對象的初始化济榨,但是A線程已經(jīng)讀取到singleInstance不為null,這時候就會導致空指針異常绿映。

第5步的代碼創(chuàng)建了一個對象擒滑,這一行代碼可以分解成3個操作:
memory = allocate();  // 1:分配對象的內(nèi)存空間
ctorInstance(memory); // 2:初始化對象
instance = memory;  // 3:設(shè)置instance指向剛分配的內(nèi)存地址

根源在于代碼中的2和3之間,可能會被重排序叉弦。例如:

memory = allocate();  // 1:分配對象的內(nèi)存空間
instance = memory;  // 3:設(shè)置instance指向剛分配的內(nèi)存地址
// 注意丐一,此時對象還沒有被初始化!
ctorInstance(memory); // 2:初始化對象
這在單線程環(huán)境下是沒有問題的淹冰,但在多線程環(huán)境下會出現(xiàn)問題库车,像上面的,B線程會看到一個還沒有被初始化的對象樱拴。

2和3的重排序不影響線程A的最終結(jié)果柠衍,但會導致線程B在第二步判斷出instance不為空,線程B接下來將訪問instance引用的對象晶乔。此時珍坊,線程B將會訪問到一個還未初始化的對象。

注:重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段正罢。

面試中有時會讓現(xiàn)場手寫一個簡單的單例模式阵漏,如果能寫出volatile方式,并且能把volatile機制講清楚,會加分不少履怯。

3回还、惡漢式

class SingleInstance2 {
    private static final SingleInstance3 singleInstance3 = new SingleInstance3();
    public static SingleInstance3 getSingleInstance3() {
        return singleInstance3;
    }
}

優(yōu)缺點:

  • 優(yōu)點:既能保證并發(fā)安全,也能保證性能
  • 缺點:類加載時就初始化虑乖,浪費內(nèi)存

4懦趋、加鎖懶漢式

class SingleInstance3 {
    private static SingleInstance2 singleInstance2;
    public static synchronized SingleInstance2 getSingleInstance2(){
        if (singleInstance2 == null) {
            singleInstance2 = new SingleInstance2();
        }
        return singleInstance2;
    }
}

優(yōu)缺點:

  • 缺點:犧牲了性能(初始化后,仍會發(fā)生線程阻塞問題)疹味,保證了并發(fā)安全
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仅叫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子糙捺,更是在濱河造成了極大的恐慌诫咱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洪灯,死亡現(xiàn)場離奇詭異坎缭,居然都是意外死亡,警方通過查閱死者的電腦和手機签钩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門掏呼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铅檩,你說我怎么就攤上這事憎夷。” “怎么了昧旨?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵拾给,是天一觀的道長。 經(jīng)常有香客問我兔沃,道長蒋得,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任乒疏,我火速辦了婚禮额衙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缰雇。我一直安慰自己入偷,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布械哟。 她就那樣靜靜地躺著疏之,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暇咆。 梳的紋絲不亂的頭發(fā)上锋爪,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天丙曙,我揣著相機與錄音,去河邊找鬼其骄。 笑死亏镰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的拯爽。 我是一名探鬼主播索抓,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毯炮!你這毒婦竟也來了逼肯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤桃煎,失蹤者是張志新(化名)和其女友劉穎篮幢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體为迈,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡三椿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葫辐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搜锰。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耿战,靈堂內(nèi)的尸體忽然破棺而出纽乱,到底是詐尸還是另有隱情,我是刑警寧澤昆箕,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站租冠,受9級特大地震影響鹏倘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜顽爹,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一纤泵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镜粤,春花似錦捏题、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至同规,卻和暖如春循狰,著一層夾襖步出監(jiān)牢的瞬間窟社,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工绪钥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灿里,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓程腹,卻偏偏與公主長得像匣吊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寸潦,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348