java基礎(chǔ)系列-單例詳解

單例模式

單例模式是我們平時(shí)經(jīng)常遇到的設(shè)計(jì)模式之一,它是一種對(duì)象創(chuàng)建模式,用于產(chǎn)生對(duì)象實(shí)例,確保一個(gè)類只實(shí)例化一次。這帶來(lái)的好處是顯而易見(jiàn)的,頻繁的new操作,會(huì)帶來(lái)新建對(duì)象造成的系統(tǒng)開(kāi)銷,尤其是一些重量級(jí)對(duì)象更是如此。
下面是單例模式的基本結(jié)構(gòu)圖:


20160604141946767.png

單例模式常用的幾種實(shí)現(xiàn)方式如下:

  • 基于synchronized實(shí)現(xiàn)
  • 基于DCL實(shí)現(xiàn)
  • 基于靜態(tài)塊的實(shí)現(xiàn)
  • 基于靜態(tài)內(nèi)部類實(shí)現(xiàn)
  • 基于枚舉的實(shí)現(xiàn)

方法一:基于synchronized實(shí)現(xiàn)

public class SynchronizedSingleton { 

  private SynchronizedSingleton(){}  
  private static SynchronizedSingleton instance;

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

這是一種最容易理解的解決方案,通過(guò)對(duì)getInstance方法加鎖的方式,確保多線程環(huán)境下instance實(shí)例的唯一性,不過(guò)加鎖帶來(lái)的性能問(wèn)題也是最大的缺陷。

方法二:基于DCL+volatile實(shí)現(xiàn)

public class DCLSingleton { 
/** * 私有的構(gòu)造函數(shù) */ 
    private DCLSingleton(){ System.out.println("instance is created!"); } 
 
     private static DCLSingleton instance;  

    public static DCLSingleton getInstance(){
       if(instance == null){ 
            synchronized(DCLSingleton.class){
               if(instance == null){
                  instance = new DCLSingleton();
               }
            }
       } 
       return instance;
   }
}

其中** 構(gòu)造函數(shù)必須是private訪問(wèn)級(jí)別的 **,防止單例類不會(huì)被其他代碼通過(guò)構(gòu)造函數(shù)實(shí)例化。其次instance成員變量和getInstance方法必須是static的。通過(guò)雙檢查機(jī)制可以滿足單例對(duì)象的構(gòu)造了,但是這種方法并不是完全正確的。主要問(wèn)題出在下面這句代碼上:

instance = new DCLSingleton();

這句話的執(zhí)行過(guò)程一般分為這么幾步:

  1. 在堆內(nèi)存中為即將實(shí)例化的對(duì)象分配內(nèi)存空間。
  2. 對(duì)象實(shí)例化操作
  3. 將內(nèi)存地址值賦給instance

這在JVM中可能出現(xiàn)指令重排的過(guò)程,因?yàn)榉峙溥^(guò)內(nèi)存后地址的值就確定了,jvm認(rèn)為步驟2和3前后調(diào)整對(duì)最終結(jié)果并不會(huì)產(chǎn)生影響,所以指令重排可能出現(xiàn)1->3->2順序的情況,當(dāng)instance被賦值了但對(duì)象實(shí)例化工作并沒(méi)有完成的時(shí)候,此時(shí)如果別的線程調(diào)用getInstance方法,由于instance!=null就會(huì)返回instance變量。這里返回的雖然是同一個(gè)對(duì)象,但是這個(gè)對(duì)象的狀態(tài)是不一致的(可能初始化還未完成)。所以** 這種方式獲得的對(duì)象并不是嚴(yán)格意義上的單例對(duì)象(同一狀態(tài)的同一個(gè)對(duì)象)。為了避免指令重排現(xiàn)象的發(fā)生,可以通過(guò)volatile來(lái)修飾instance變量定義如下

private static volatile DCLSingleton instance;

方法三:基于靜態(tài)成員變量的實(shí)現(xiàn)方案

public class StaticSingleton { 
      private StaticSingleton(){ System.out.println("instance is created!"); } 

      /** * instance實(shí)例化是在類加載階段進(jìn)行的,天生線程友好,保證對(duì)象唯一性。 */ 
      private static StaticSingleton instance = new StaticSingleton();  
      public static StaticSingleton getInstance(){ 
             return instance; 
      }
}

靜態(tài)成員變量的實(shí)現(xiàn)方案主要利用了靜態(tài)成員變量的初始化時(shí)機(jī)是在類的加載階段這一特性。我們都知道類的加載階段包括(加載-連接-初始化),這里重點(diǎn)說(shuō)下初始化階段jvm做的工作,初始化階段會(huì)執(zhí)行所有類變量(靜態(tài)成員變量)的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的內(nèi)容。上面例子中也可以改用靜態(tài)語(yǔ)句塊實(shí)現(xiàn),如下:

      private static StaticSingleton instance;       
      static{ 
              instance = new StaticSingleton();
      }

這種實(shí)現(xiàn)方案可以滿足單例模式的基本需求,但是如果我們某些情況調(diào)用了單例類的其他的靜態(tài)方法,則類的實(shí)例化操作同樣會(huì)被觸發(fā),如下所示:

public class StaticSingleton { 
        private StaticSingleton(){ System.out.println("instance is created!"); }
        private static StaticSingleton instance = new StaticSingleton();  

        public static StaticSingleton getInstance(){ return instance; } 

        public static String otherMethod(){ return "msg"; }}

        //outside code 
        public static void main(String args[]){ 
            //call otherMethod 
            StaticSingleton.otherMethod(); 
    }
}
輸出:instance is created!

我們希望獲得一種延遲加載機(jī)制,即我什么時(shí)候調(diào)用getInstance方法,單例對(duì)象的實(shí)例化才被觸發(fā)。此時(shí)可以使用靜態(tài)內(nèi)部類來(lái)實(shí)現(xiàn)LazyInit這一特性。

方法四:基于靜態(tài)內(nèi)部類實(shí)現(xiàn)

public class StaticSingleton {  

       private StaticHolderSingleton(){ System.out.println("instance is created!"); }  

       private static StaticSingleton instance ;  

       /** * 使用靜態(tài)內(nèi)部類實(shí)現(xiàn)單例 *  */ 
       private static class SingletonHolder{ 
            private static StaticSingleton instance = new  StaticSingleton(); 
      } 
      public static StaticSingleton getInstance(){ 
              return SingletonHolder.instance; 
       }
}

在這種實(shí)現(xiàn)方案中,如果調(diào)用getInstance之外的方法,instance變量并不會(huì)初始化。這就達(dá)到了lazy init的目的。

方法五:基于枚舉類型的實(shí)現(xiàn)

public class EnumSingleton{
/** * 枚舉類型實(shí)現(xiàn) */ 
  enum SingletonHoler{ 
          holder; 

          private SingletonDemo instance;  
          
          SingletonHoler(){ instance = new SingletonDemo();  } 
           
          public SingletonDemo getInstance(){ return instance; } 
   } 

    public static SingletonDemo getInstance(){ 
          return SingletonHoler.holder.getInstance(); 
    }
}

總結(jié):方法一使用synchronized加鎖,保證了getInstance()方法調(diào)用的串行化,從而保證了實(shí)例的唯一性。最簡(jiǎn)單,但高并發(fā)情況下由于鎖的競(jìng)爭(zhēng)效率較低。
方法二:通過(guò)雙檢查鎖機(jī)制,也保證了實(shí)例的唯一性。這里要注意的是用volatile修飾成員變量。
方法三和方法四原理一致,利用了靜態(tài)成員和靜態(tài)塊在類初始化的時(shí)候就執(zhí)行這一特性。保證程序運(yùn)行時(shí)獲取的對(duì)象唯一性。
方法五和方法三四類似,使用枚舉類型時(shí),構(gòu)造方法會(huì)被自動(dòng)調(diào)用,用這個(gè)特性實(shí)現(xiàn)單例模式。

以上方法都可以在正常情況下實(shí)現(xiàn)單例,但是一些極端情況下還是會(huì)出現(xiàn)問(wèn)題。具體情況如下:

通過(guò)反射調(diào)用私有構(gòu)造函數(shù)來(lái)實(shí)例化一個(gè)對(duì)象。

/** * 利用反射生成對(duì)象 */ 
@Test public void ReflectSingletionTest() throws Exception{ 
     //通過(guò)getInstance方法獲取對(duì)象instance1 

      DCLSingleton instance1 = DCLSingleton.getInstance(); 
      
      Class cls = Class.forName("edu.ouc.pattern.singleton.DCLSingleton"); 

      Constructor c = cls.getDeclaredConstructor(); c.setAccessible(true); 

      //通過(guò)反射獲取對(duì)象
       instance2 DCLSingleton instance2 = (DCLSingleton) c.newInstance(); 
      
       Assert.assertEquals(instance1, instance2); }

結(jié)果:
java.lang.AssertionError: expected:edu.ouc.pattern.singleton.DCLSingleton@4eaa3152but was:edu.ouc.pattern.singleton.DCLSingleton@4586793e

結(jié)果顯示兩次獲取的對(duì)象并不是同一個(gè)對(duì)象,這顯然不是我們想要的。為了避免這一情況,可以強(qiáng)制構(gòu)造函數(shù)的調(diào)用次數(shù)僅為一次,否則拋出相應(yīng)的異常。實(shí)現(xiàn)代碼片段如下:

   //新增isSingle變量,初始為false
   private volatile static Boolean isSingle = false; 
   /** * 私有的構(gòu)造函數(shù) */ 
   private DCLSingleton(){ 
         synchronized(isSingle){ 
              if(!isSingle){ 
                  isSingle = true; 
              }else{
                      try {  throw new Exception("duplicate instance exception : " + DCLSingleton.class.getName());  
                      } catch (Exception e) {  e.printStackTrace();  } 
               }
          }
 }

此時(shí)再運(yùn)行ReflectSingletionTest()方法,結(jié)果如下:

java.lang.Exception: duplicate instance exception :
edu.ouc.pattern.singleton.DCLSingleton> at
edu.ouc.pattern.singleton.DCLSingleton.<init>(DCLSingleton.java:14)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 場(chǎng)景問(wèn)題# 1.1 讀取配置文件的內(nèi)容## 考慮這樣一個(gè)應(yīng)用,讀取配置文件的內(nèi)容。 很多應(yīng)用項(xiàng)目,都有與應(yīng)用相...
    七寸知架構(gòu)閱讀 6,975評(píng)論 12 68
  • 1 單例模式的動(dòng)機(jī) 對(duì)于一個(gè)軟件系統(tǒng)的某些類而言,我們無(wú)須創(chuàng)建多個(gè)實(shí)例。舉個(gè)大家都熟知的例子——Windows任務(wù)...
    justCode_閱讀 1,558評(píng)論 2 9
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡(jiǎn)單、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮?jiǎn)潔易懂,是項(xiàng)目中最...
    成熱了閱讀 4,536評(píng)論 4 34
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,687評(píng)論 18 399
  • 前言 本文主要參考 那些年,我們一起寫過(guò)的“單例模式”。 何為單例模式? 顧名思義,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,627評(píng)論 1 8

友情鏈接更多精彩內(nèi)容