單例模式:層層剖析尋找最高效安全的單例
阿新 • • 發佈:2018-11-04
問題來源
什麼是單例?它的運用場景是什麼?
單例模式是指保證在系統中只存在某類唯一物件。運用場景隨處可見,例如工具類、Spring容器預設new物件等。
單例模式有幾種實現方式?
餓漢式、懶漢式、雙重檢查鎖式、內部類式、列舉式。
推薦使用方式?
餓漢式、內部類式。
餓漢式
餓漢式顧名思義餓,那麼當應用程式一開始類載入,類的物件立馬例項化載入至JVM。
public class SingletonClass { /** * 優點:呼叫效率高。 * 缺點:沒有延遲載入。 */ private static SingletonClass instance =new SingletonClass(); public static SingletonClass getInstance(){ return instance; } }
為什麼呼叫效率高?沒有延遲載入?
答:假設在高併發的場景下,有10W+併發呼叫,不需要同步處理。可以直接在堆記憶體直接獲取物件不需要任何等待。
同樣,它沒有延遲載入,如果它是需要消耗很大記憶體的物件,最開始就載入入堆記憶體,而使用者暫時不需要。這樣就會嚴重佔用堆記憶體,影響執行效率。
懶漢式
導引:腦洞大開的程式設計師們說:上述問題還不簡單,當呼叫的時候在new物件不就行。於是出現了懶漢式的雛形版本。
public class SingletonClass { private static SingletonClass instance; public static SingletonClass getInstance(){ if(null==instance){ instance=new SingletonClass(); } return instance; } }
懶漢式顧名思義懶,就是延遲載入,當被呼叫的時候再例項化。
問題:如果你是初出茅廬的應屆生寫成這樣,估計面試官也不會追究什麼。如果你是有一年工作年限的程式設計師,估計面試官就會聲討你了。假設,併發數10W+,它就將被蹂躪的不堪入目。那麼我們需要怎麼解決呢?加上同步操作就大功告成。
public class SingletonClass { //呼叫效率低、延遲載入 private static SingletonClass instance; public static synchronized SingletonClass getInstance(){ if(null==instance){ instance=new SingletonClass(); } return instance; } }
問題:從效率維度考慮,估計這樣已經完美了吧?但是,從安全緯度考慮,依然隱隱約約存在問題。如果是接觸過反射、反序列化的同學,我們一起來繼續探討。
/**
* 通過反射破壞懶漢式單例
* @author aaron
*/
public class Client {
public static void main(String[] args) throws Exception {
SingletonClass clazzOne=SingletonClass.getInstance();
SingletonClass clazzTwo=SingletonClass.getInstance();
System.out.println("clazzOne-hasCode:"+clazzOne.hashCode());
System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode());
Class<SingletonClass> clazz=(Class<SingletonClass>)Class.forName("singleton.SingletonClass");
Constructor<SingletonClass> c=clazz.getConstructor(null);
c.setAccessible(true);
SingletonClass clazzThree=c.newInstance();
SingletonClass clazzFour=c.newInstance();
System.out.println("clazzThree-hasCode:"+clazzThree.hashCode());
System.out.println("clazzFour-hasCode:"+clazzFour.hashCode());
}
}
public class SingletonClass implements Serializable{
private static SingletonClass instance;
public static synchronized SingletonClass getInstance(){
if(null==instance){
instance=new SingletonClass();
}
return instance;
}
public static void main(String[] args) throws Exception {
SingletonClass clazzOne=SingletonClass.getInstance();
SingletonClass clazzTwo=SingletonClass.getInstance();
System.out.println("clazzOne-hasCode:"+clazzOne.hashCode());
System.out.println("clazzTwo-hasCode:"+clazzTwo.hashCode());
FileOutputStream fos=new FileOutputStream(new File("f:/test.txt"));
ObjectOutputStream bos=new ObjectOutputStream(fos);
bos.writeObject(clazzOne);
bos.close();
fos.close();
FileInputStream fis=new FileInputStream(new File("f:/test.txt"));
ObjectInputStream bis=new ObjectInputStream(fis);
SingletonClass clazzThree=(SingletonClass) bis.readObject();
System.out.println("clazzThree-hasCode:"+clazzThree.hashCode());
}
}
問題:這麼輕易就被破解了?那怎麼解決呢?
public class SingletonClass implements Serializable{
private static SingletonClass instance;
private SingletonClass(){
//防止被反射
if(null!=instance){
throw new RuntimeException();
}
}
public static synchronized SingletonClass getInstance(){
if(null==instance){
instance=new SingletonClass();
}
return instance;
}
//當沒有定義這方法時,反序列化預設是重新new物件。
//反序列化時,如果定義了readResolve()則直接返回此方法指定的物件。而不需要單獨再建立新物件!
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
雙重檢查鎖與內部類
雙重檢查鎖與內部類的方式:緣由懶漢式、餓漢式要麼存在呼叫效率低或者執行效率低問題。而這兩種方式取前兩者的優點為自己所用。
/**
* 單例模式-雙重檢查鎖
* @author aaron
*/
public class SingletonClass{
private static SingletonClass instance;
public static SingletonClass getInstance(){
if(null==instance){
synchronized (SingletonClass.class) {
if(instance==null){
instance=new SingletonClass();
}
}
}
return instance;
}
}
問題:緣由JVM對於此種方式的同步控制,並不穩定,當高併發的時候,可能會出現問題,並不推薦使用這種方式。理論上來說,它是不存在問題的。
/**
* 單例模式-內部類的方式
* @author aaron
*/
public class SingletonClass{
private static class InnerClass{
public static SingletonClass instance=new SingletonClass();
}
public static SingletonClass getInstance(){
return InnerClass.instance;
}
}
/**
* 單例模式-列舉的方式
* @author aaron
*/
public enum SingletonClass{
INSTANCE
}