問題來源
什么是單例?它的運用場景是什么?
單例模式是指保證在系統中只存在某類唯一對象。運用場景隨處可見,例如工具類、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><SingletonClass> clazz=(Class<SingletonClass>)Class.forName("singleton.SingletonClass"<span style="color: #000000;">);
Constructor</span><SingletonClass> 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/
您的支持是對博主深入思考總結的最大鼓勵。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,尊重作者的勞動成果。
