java中單例模式是一種常見的設計模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三
種。java
單例模式有如下特色:面試
顧名思義,餓漢式就是在第一次引用該類的時候就建立對象實例,而無論實際是否須要建立。代碼如
下:
下面看一個示例:設計模式
public class Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; } }
這樣作的好處是編寫簡單,可是沒法作到延遲建立對象。可是咱們不少時候都但願對象能夠儘量地
延遲加載,從而減少負載,因此就須要下面的懶漢式緩存
單線程寫法
public class Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; }}
這種寫法是最簡單的,由私有構造器和一個公有靜態工廠方法構成,在工廠方法中對singleton進行null
判斷,若是是null就new一個出來,最後返回singleton對象。這種方法能夠實現延時加載,可是有一個
致命弱點:線程不安全。若是有兩條線程同時調用getSingleton()方法,就有很大可能致使重複建立對
象。安全
public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton() { if(singleton == null) singleton = new Singleton(); return singleton; } }
這種寫法考慮了線程安全,將對singleton的null判斷以及new的部分使用synchronized進行加鎖。同
時,對singleton對象使用volatile關鍵字進行限制,保證其對全部線程的可見性,而且禁止對其進行指
令重排序優化。如此便可從語義上保證這種單例模式寫法是線程安全的。注意,這裏說的是語義上,實
際使用中仍是存在小坑的多線程
public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
雖然上面這種寫法是能夠正確運行的,可是其效率低下,仍是沒法實際應用。由於每次調用
getSingleton()方法,都必須在synchronized這裏進行排隊,而真正遇到須要new的狀況是很是少的。
因此,就誕生了第三種寫法架構
public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
這種寫法被稱爲「雙重檢查鎖」,顧名思義,就是在getSingleton()方法中,進行兩次null檢查。看似多
此一舉,但實際上卻極大提高了併發度,進而提高了性能。爲何能夠提升併發度呢?就像上文說的,
在單例中new的狀況很是少,絕大多數都是能夠並行的讀操做。所以在加鎖前多進行一次null檢查就可
以減小絕大多數的加鎖操做,執行效率提升的目的也就達到了併發
那麼,這種寫法是否是絕對安全呢?前面說了,從語義角度來看,並無什麼問題。可是其實仍是有
坑。說這個坑以前咱們要先來看看 volatile 這個關鍵字。其實這個關鍵字有兩層語義。第一層語義相信
你們都比較熟悉,就是可見性。可見性指的是在一個線程中對該變量的修改會立刻由工做內存( Work
Memory )寫回主內存( Main Memory ),因此會立刻反應在其它線程的讀取操做中。順便一提,工
做內存和主內存能夠近似理解爲實際電腦中的高速緩存和主存,工做內存是線程獨享的,主存是線程共
享的。 volatile 的第二層語義是禁止指令重排序優化。你們知道咱們寫的代碼(尤爲是多線程代碼),
因爲編譯器優化,在實際執行的時候可能與咱們編寫的順序不一樣。編譯器只保證程序執行結果與源代碼
相同,卻不保證明際指令的順序與源代碼相同。這在單線程看起來沒什麼問題,然而一旦引入多線程,
這種亂序就可能致使嚴重問題。 volatile 關鍵字就能夠從語義上解決這個問題。
注意,前面反覆提到 「 從語義上講是沒有問題的 」 ,可是很不幸,禁止指令重排優化這條語義直到 jdk1.5
之後才能正確工做。此前的 JDK 中即便將變量聲明爲 volatile 也沒法徹底避免重排序所致使的問題。所
以,在 jdk1.5 版本前,雙重檢查鎖形式的單例模式是沒法保證線程安全的。高併發
那麼,有沒有一種延時加載,而且能保證線程安全的簡單寫法呢?咱們能夠把 Singleton 實例放到一個
靜態內部類中,這樣就避免了靜態實例在 Singleton 類加載的時候就建立對象,而且因爲靜態內部類只
會被加載一次,因此這種寫法也是線程安全的:性能
public class Singleton { private static class Holder { private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getSingleton(){ return Holder.singleton; } }
可是,上面提到的全部實現方式都有兩個共同的缺點:
都須要額外的工做(Serializable、transient、readResolve())來實現序列化,不然每次反序列化一 個序列化的對象實例時都會建立一個新的實例。
可能會有人使用反射強行調用咱們的私有構造器(若是要避免這種狀況,能夠修改構造器,讓它在 建立第二個實例的時候拋異常)。
登記式單例實際上維護了一組單例類的實例,將這些實例存放在一個 Map (登記薄)中,對於已經登記
過的實例,則從 Map 直接返回,對於沒有登記的,則先登記,而後返回。
public class Singleton { private static Map<String, Singleton> map = new HashMap<String, Singleton> (); static { Singleton single = new Singleton(); map.put(single.getClass().getName(), single); } // 保護的默認構造子 protected Singleton() { } // 靜態工廠方法,返還此類唯一的實例 public static Singleton getInstance(String name) { if (name == null) { name = Singleton.class.getName(); System.out.println("name == null" + "--->name=" + name); } if (map.get(name) == null) { try { map.put(name, (Singleton) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } }
它用的比較少,另外其實內部實現仍是用的餓漢式單例,由於其中的 static 方法塊,它的單例在類被裝
載的時候就被實例化了。
固然,還有一種更加優雅的方法來實現單例模式,那就是枚舉寫法
public enum Singleton { INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } }
使用枚舉除了線程安全和防止反射強行調用構造器以外,還提供了自動序列化機制,防止反序列化
的時候建立新的對象。所以推薦儘量地使用枚舉來實現單例。
最後,無論採起何種方案,請時刻牢記單例的三大要點:
線程安全
延遲加載
序列化與反序列化安全
在文章的最後做者爲你們整理了不少資料!包括java核心知識點+全套架構師學習資料和視頻+一線大廠面試寶典+面試簡歷模板+阿里美團網易騰訊小米愛奇藝快手嗶哩嗶哩面試題+Spring源碼合集+Java架構實戰電子書等等!
所有免費分享給你們,有須要的朋友關注公衆號:【前程有光】自取!