<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          單例模式(含多線程處理)

          共 5037字,需瀏覽 11分鐘

           ·

          2021-04-14 14:25

          走過路過不要錯過

          點擊藍字關(guān)注我們


          單例,顧名思義一個類只有一個實例。為什么要使用單例模式,或者說什么樣的類可以做成單例的?在工作中我發(fā)現(xiàn),使用單例模式的類都有一個共同點,那就是這個類沒有狀態(tài),也就是說無論你實例化多少個對象,其實都是一樣的。又或者是一個類需要頻繁實例化然后銷毀對象。還有很重要的一點,如果這個類有多個實例的話,會產(chǎn)生程序錯誤或者不符合業(yè)務(wù)邏輯。這種情況下,如果我們不把類做成單例,程序中就會存在多個一模一樣的實例,這樣會造成內(nèi)存資源的浪費,而且容易產(chǎn)生程序錯誤。總結(jié)一下,判斷一個類是否要做成單例,最簡單的一點就是,如果這個類有多個實例會產(chǎn)生錯誤,或者在整個應(yīng)用程序中,共享一份資源。

          在實際開發(fā)中,一些資源管理器、數(shù)據(jù)庫連接等常常設(shè)計成單例模式,避免實例重復(fù)創(chuàng)建。實現(xiàn)單例有幾種常用的方式,下面我們來探討一下他們各自的優(yōu)劣。

          第一種方式:懶漢式單例

          public class Singleton {    //一個靜態(tài)實例    private static Singleton singleton;    //私有構(gòu)造方法    private Singleton(){
          } //提供一個公共靜態(tài)方法來獲取一個實例 public static Singleton getInstance(){
          if(singleton == null ){
          singleton = new Singleton(); }
          return singleton;
          }}

          在不考慮并發(fā)的情況下,這是標準的單例構(gòu)造方式,它通過以下幾個要點來保證我們獲得的實例是單一的。

          1、靜態(tài)實例,靜態(tài)的屬性在內(nèi)存中是唯一的;

          2、私有的構(gòu)造方法,這就保證了不能人為的去調(diào)用構(gòu)造方法來生成一個實例;

          3、提供公共的靜態(tài)方法來返回一個實例, 把這個方法設(shè)置為靜態(tài)的是有原因的,因為這樣我們可以通過類名來直接調(diào)用此方法(此時我們還沒有獲得實例,無法通過實例來調(diào)用方法),而非靜態(tài)的方法必須通過實例來調(diào)用,因此這里我們要把它聲明為靜態(tài)的方法通過類名來調(diào)用;

          4、判斷只有持有的靜態(tài)實例為null時才通過構(gòu)造方法產(chǎn)生一個實例,否則直接返回。

          在多線程環(huán)境下,這種方式是不安全,通過自己的測試,多個線程同時訪問它可能生成不止一個實例,我們通過程序來驗證這個問題:

          public class Singleton {    //一個靜態(tài)實例    private static Singleton singleton;    //私有構(gòu)造方法    private Singleton(){
          } //提供一個公共靜態(tài)方法來獲取一個實例 public static Singleton getInstance(){
          if(singleton == null ){
          try { Thread.sleep(5000); //模擬線程在這里發(fā)生阻塞 } catch (InterruptedException e) { e.printStackTrace(); }
          singleton = new Singleton(); }
          return singleton;
          }}

          測試類:

          public class TestSingleton {
          public static void main(String[] args) {
          Thread t1 = new MyThread(); Thread t2 = new MyThread();
          t1.start(); t2.start(); }
          }

          class MyThread extends Thread{
          @Override public void run() {
          System.out.println(Singleton.getInstance()); //打印生成的實例,會輸出實例的類名+哈希碼值
          }}

          執(zhí)行該測試類,輸出的結(jié)果如下:

              

          從以上結(jié)果可以看出,輸出兩個實例并且實例的hashcode值不相同,證明了我們獲得了兩個不一樣的實例。這是什么原因呢?我們生成了兩個線程同時訪問getInstance()方法,在程序中我讓線程睡眠了5秒,是為了模擬線程在此處發(fā)生阻塞,當?shù)谝粋€線程t1進入getInstance()方法,判斷完singleton為null,接著進入if語句準備創(chuàng)建實例,同時在t1創(chuàng)建實例之前,另一個線程t2也進入getInstance()方法,此時判斷singleton也為null,因此線程t2也會進入if語句準備創(chuàng)建實例,這樣問題就來了,有兩個線程都進入了if語句創(chuàng)建實例,這樣就產(chǎn)生了兩個實例。

          為了避免這個問題,在多線程情況下我們要考慮線程同步問題了,最簡單的方式當然是下面這種方式,直接讓整個方法同步:

          public class Singleton {    //一個靜態(tài)實例    private static Singleton singleton;    //私有構(gòu)造方法    private Singleton(){
          } //提供一個公共靜態(tài)方法來獲取一個實例 public static synchronized Singleton getInstance(){
          if(singleton == null ){
          try { Thread.sleep(5000); //模擬線程在這里發(fā)生阻塞 } catch (InterruptedException e) { e.printStackTrace(); }
          singleton = new Singleton(); }
          return singleton;
          }}

          我們通過給getInstance()方法加synchronized關(guān)鍵字來讓整個方法同步,我們同樣可以執(zhí)行上面給出的測試類來進行測試,打印結(jié)果如下:

              

          從測試結(jié)果可以看出,兩次調(diào)用getInstance()方法返回的是同一個實例,這就達到了我們單例的目的。這種方式雖然解決了多線程同步問題,但是并不推薦采用這種設(shè)計,因為沒有必要對整個方法進行同步,這樣會大大增加線程等待的時間,降低程序的性能。我們需要對這種設(shè)計進行優(yōu)化,這就是我們下面要討論的第二種實現(xiàn)方式。

          第二種方式:雙重校驗鎖

          由于對整個方法加鎖的設(shè)計效率太低,我們對這種方式進行優(yōu)化:

          public class Singleton {    //一個靜態(tài)實例    private static Singleton singleton;    //私有構(gòu)造方法    private Singleton(){
          } //提供一個公共靜態(tài)方法來獲取一個實例 public static Singleton getInstance(){
          if(singleton == null ){
          synchronized(Singleton.class){
          if(singleton == null){
          singleton = new Singleton();
          } } }
          return singleton;
          }}

          跟上面那種糟糕的設(shè)計相比,這種方式就好太多了。因為這里只有當singleton為null時才進行同步,當實例已經(jīng)存在時直接返回,這樣就節(jié)省了無謂的等待時間,提高了效率。注意在同步塊中,我們再次判斷了singleton是否為空,下面解釋下為什么要這么做。假設(shè)我們?nèi)サ暨@個判斷條件,有這樣一種情況,當兩個線程同時進入if語句,第一個線程t1獲得線程鎖執(zhí)行實例創(chuàng)建語句并返回一個實例,接著第二個線程t2獲得線程鎖,如果這里沒有實例是否為空的判斷條件,t2也會執(zhí)行下面的語句返回另一個實例,這樣就產(chǎn)生了多個實例。因此這里必須要判斷實例是否為空,如果已經(jīng)存在就直接返回,不會再去創(chuàng)建實例了。這種方式既保證了線程安全,也改善了程序的執(zhí)行效率。

          第三種方式:靜態(tài)內(nèi)部類

          public class Singleton {    //靜態(tài)內(nèi)部類    private static class SingletonHolder{        private static Singleton singleton = new Singleton();    }    //私有構(gòu)造方法    private Singleton(){
          } //提供一個公共靜態(tài)方法來獲取一個實例 public static Singleton getInstance(){
          return SingletonHolder.singleton;
          }}

          這種方式利用了JVM的類加載機制,保證了多線程環(huán)境下只會生成一個實例。當某個線程訪問getInstance()方法時,執(zhí)行語句訪問內(nèi)部類SingletonHolder的靜態(tài)屬性singleton,這也就是說當前類主動使用了改靜態(tài)屬性,JVM會加載內(nèi)部類并初始化內(nèi)部類的靜態(tài)屬性singleton,在這個初始化過程中,其他的線程是無法訪問該靜態(tài)變量的,這是JVM內(nèi)部幫我們做的同步,我們無須擔心多線程問題,并且這個靜態(tài)屬性只會初始化一次,因此singleton是單例的。

          第四種方式:餓漢式

          public class Singleton {    //一個靜態(tài)實例    private static Singleton singleton = new Singleton();    //私有構(gòu)造方法    private Singleton(){
          } //提供一個公共靜態(tài)方法來獲取一個實例 public static Singleton getInstance(){
          return singleton;
          }}

          這種方式也是利用了JVM的類加載機制,在單例類被加載時就初始化一個靜態(tài)實例,因此這種方式也是線程安全的。這種方式存在的問題就是,一旦Singleton類被加載就會產(chǎn)生一個靜態(tài)實例,而類被加載的原因有很多種,事實上我們可能從始至終都沒有使用這個實例,這樣會造成內(nèi)存的浪費。在實際開發(fā)中,這個問題影響不大。

          以上內(nèi)容介紹了幾種常見的單例模式的實現(xiàn)方式,分析了在多線程情況下的處理方式, 在工作中可根據(jù)實際需要選擇合適的實現(xiàn)方式。還有一種利用枚舉來實現(xiàn)單例的方式,在工作中很少有人這樣寫過,不做探討。




          往期精彩推薦



          騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學?那是因為你沒認真看完這篇文章


          END


          關(guān)注作者微信公眾號 —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識以及最新面試寶典


          你點的每個好看,我都認真當成了


          看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力


          作者:風中程序猿

          出處:https://www.cnblogs.com/fangfuhai/p/6666850.html

          瀏覽 20
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  东京热亚洲无码 | 免费操逼 | 青娱乐人人操 | 色九月婷婷国产 | 一区免费在线 |