單例模式:層層剖析尋找最高效安全的單例


問題來源

  什么是單例?它的運用場景是什么?

  單例模式是指保證在系統中只存在某類唯一對象。運用場景隨處可見,例如工具類、Spring容器默認new對象等。

  單例模式有幾種實現方式?

  餓漢式、懶漢式、雙重檢查鎖式、內部類式、枚舉式。

  推薦使用方式?

  餓漢式、內部類式。

餓漢式

  餓漢式顧名思義餓,那么當應用程序一開始類加載,類的對象立馬實例化加載至JVM。

 1 public class SingletonClass {  2     /**
 3  * 優點:調用效率高。  4  * 缺點:沒有延遲加載。  5      */
 6     private static SingletonClass instance =new SingletonClass();  7     
 8     public static SingletonClass getInstance(){  9         return instance; 10  } 11 }

   為什么調用效率高?沒有延遲加載?

  答:假設在高並發的場景下,有10W+並發調用,不需要同步處理。可以直接在堆內存直接獲取對象不需要任何等待。

    同樣,它沒有延遲加載,如果它是需要消耗很大內存的對象,最開始就加載入堆內存,而用戶暫時不需要。這樣就會嚴重占用堆內存,影響運行效率。

 懶漢式

  導引:腦洞大開的程序員們說:上述問題還不簡單,當調用的時候在new對象不就行。於是出現了懶漢式的雛形版本。

public class SingletonClass { private static SingletonClass instance; 
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> SingletonClass getInstance(){
    </span><span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span>==<span style="color: #000000;">instance){
        instance</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> SingletonClass();
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;
}

}

   懶漢式顧名思義懶,就是延遲加載,當被調用的時候再實例化。

  問題:如果你是初出茅廬的應屆生寫成這樣,估計面試官也不會追究什么。如果你是有一年工作年限的程序員,估計面試官就會聲討你了。假設,並發數10W+,它就將被蹂躪的不堪入目。那么我們需要怎么解決呢?加上同步操作就大功告成。

 1 public class SingletonClass {  2     
 3     //調用效率低、延遲加載
 4     private static SingletonClass instance;  5     
 6     public static synchronized SingletonClass getInstance(){  7         if(null==instance){  8             instance=new SingletonClass();  9  } 10         return instance; 11  } 12 }

   問題:從效率維度考慮,估計這樣已經完美了吧?但是,從安全緯度考慮,依然隱隱約約存在問題。如果是接觸過反射、反序列化的同學,我們一起來繼續探討。

/** * 通過反射破壞懶漢式單例 * @author aaron */
public class Client { public static void main(String[] args) throws Exception { SingletonClass clazzOne=SingletonClass.getInstance(); SingletonClass clazzTwo=SingletonClass.getInstance(); 
    System.out.println(</span>"clazzOne-hasCode:"+<span style="color: #000000;">clazzOne.hashCode());
    System.out.println(</span>"clazzTwo-hasCode:"+<span style="color: #000000;">clazzTwo.hashCode());
    
    Class</span>&lt;SingletonClass&gt; clazz=(Class&lt;SingletonClass&gt;)Class.forName("singleton.SingletonClass"<span style="color: #000000;">);
    Constructor</span>&lt;SingletonClass&gt; c=clazz.getConstructor(<span style="color: #0000ff;">null</span><span style="color: #000000;">);
    c.setAccessible(</span><span style="color: #0000ff;">true</span><span style="color: #000000;">);
    SingletonClass clazzThree</span>=<span style="color: #000000;">c.newInstance();
    SingletonClass clazzFour</span>=<span style="color: #000000;">c.newInstance();
    System.out.println(</span>"clazzThree-hasCode:"+<span style="color: #000000;">clazzThree.hashCode());
    System.out.println(</span>"clazzFour-hasCode:"+<span style="color: #000000;">clazzFour.hashCode());
}

}

 

 1 public class SingletonClass implements Serializable{  2     
 3     private static SingletonClass instance;  4     
 5     public static synchronized SingletonClass getInstance(){  6         if(null==instance){  7             instance=new SingletonClass();  8  }  9         return instance; 10  } 11     
12     public static void main(String[] args) throws Exception { 13         SingletonClass clazzOne=SingletonClass.getInstance(); 14         SingletonClass clazzTwo=SingletonClass.getInstance(); 15         System.out.println("clazzOne-hasCode:"+clazzOne.hashCode()); 16         System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode()); 17         
18     
19         FileOutputStream fos=new FileOutputStream(new File("f:/test.txt")); 20         ObjectOutputStream bos=new ObjectOutputStream(fos); 21  bos.writeObject(clazzOne); 22  bos.close(); 23  fos.close(); 24         
25         FileInputStream fis=new FileInputStream(new File("f:/test.txt")); 26         ObjectInputStream bis=new ObjectInputStream(fis); 27         SingletonClass clazzThree=(SingletonClass) bis.readObject(); 28         System.out.println("clazzThree-hasCode:"+clazzThree.hashCode()); 29  } 30 }

 

  問題:這么輕易就被破解了?那怎么解決呢?

public class SingletonClass implements Serializable{ 
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> SingletonClass instance;

</span><span style="color: #0000ff;">private</span><span style="color: #000000;"> SingletonClass(){
    </span><span style="color: #008000;">//</span><span style="color: #008000;">防止被反射</span>
    <span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span>!=<span style="color: #000000;">instance){
        </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> RuntimeException();
    }
}

</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">synchronized</span><span style="color: #000000;"> SingletonClass getInstance(){
    </span><span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span>==<span style="color: #000000;">instance){
        instance</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> SingletonClass();
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;
}

</span><span style="color: #008000;">//</span><span style="color: #008000;">當沒有定義這方法時,反序列化默認是重新new對象。
</span><span style="color: #008000;">//</span><span style="color: #008000;">反序列化時,如果定義了readResolve()則直接返回此方法指定的對象。而不需要單獨再創建新對象!</span>
<span style="color: #0000ff;">private</span> Object readResolve() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> ObjectStreamException{
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> instance;
}

}

 雙重檢查鎖與內部類

  雙重檢查鎖與內部類的方式:緣由懶漢式、餓漢式要么存在調用效率低或者運行效率低問題。而這兩種方式取前兩者的優點為自己所用。

 1 /**
 2  * 單例模式-雙重檢查鎖  3  * @author aaron  4  */
 5 public class SingletonClass{  6     private static SingletonClass instance;  7     
 8     public static SingletonClass getInstance(){  9         if(null==instance){ 10             synchronized (SingletonClass.class) { 11                 if(instance==null){ 12                     instance=new SingletonClass(); 13  } 14  } 15  } 16         return instance; 17  } 18 }

   問題:緣由JVM對於此種方式的同步控制,並不穩定,當高並發的時候,可能會出現問題,並不推薦使用這種方式。理論上來說,它是不存在問題的。

 1 /**
 2  * 單例模式-內部類的方式  3  * @author aaron  4  */
 5 public class SingletonClass{  6     
 7     private static class InnerClass{  8         public static SingletonClass instance=new SingletonClass();  9  } 10     
11     public static SingletonClass getInstance(){ 12         return InnerClass.instance; 13  } 14 }
1 /**
2  * 單例模式-枚舉的方式 3  * @author aaron 4  */
5 public enum SingletonClass{ 6  INSTANCE 7 }

版權聲明

  作者:邱勇Aaron

  出處:http://www.cnblogs.com/qiuyong/

  您的支持是對博主深入思考總結的最大鼓勵。

  本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,尊重作者的勞動成果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM