文章介紹了單例模式五種實現的方式,分別是懶漢,餓漢,靜態內部類,雙重檢驗鎖以及枚舉實現方式,並主要關心加載時機以及線程安全。首先,通俗點講,餓漢就是這個類還沒被使用到的時候,實例已經建立好了;而懶漢是使用到的時候才建立對應的實例。線程安全方面主要考慮實例化時候是否確保一個實例,對於單例類中其餘方法的線程安全不予考慮。數據庫
先來一個最直觀的代碼:設計模式
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //若是尚未被實例化過,就實例化一個,而後返回 if(instance == null){ instance = new Singleton(); } return instance; } }
這段代碼裏,咱們沒有考慮線程安全,因此可能就會產生多個實例,可是這個例子能很好的表達單例模式的思想,就是保持只有一個實例,先理解了基礎,咱們再一步步發展。安全
因此線程安全的事情仍是得解決啊~因而有了如下的代碼:性能
public class Singleton { private static Singleton instance = null; private Singleton(){} public static synchronized Singleton getInstance(){ //若是尚未被實例化過,就實例化一個,而後返回 if(instance == null){ instance = new Singleton(); } return instance; } }
這段代碼只加了一個關鍵字synchronized用於確保getInstance方法線程安全,可是這種方式問題很大啊,畢竟全部的線程到了這個方法全得排隊等着,對性能的損耗很是大,不過不要緊,咱們這裏着重先解決掉線程安全的問題,接下來會有辦法解決這個效率低下的問題(若是你着急那就直接去看雙重校驗鎖吧...)學習
懶漢模式將實例化的時機放到了須要使用的時候(餓漢是類加載了就有實例),也就是「延遲加載」,相比餓漢,能避免了在加載的時候實例化有可能用不到的實例,可是問題也很明顯,咱們要花精力去解決線程安全的問題。spa
餓漢模式相比懶漢模式,在類加載的時候就已經存在一個實例,舉個例子,好比數據庫鏈接吧,懶漢就是第一次訪問數據庫的時候我纔去建立一個鏈接,而餓漢呢,是你程序啓動了,類加載好了的時候,我已經有個鏈接了,你用不用不必定了,因此餓漢的缺點也就出來了:可能會產生不少無用的實例。線程
public class Singleton { //類加載的時候instance就已經指向了一個實例 private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
那麼加載時機的問題咱們已經說過了,接下來就是線程安全了,代碼裏咱們並無看見synchronized關鍵字,那麼這種方式是如何確保線程安全的呢,這個就是JVM類加載的特性了,JVM在加載類的時候,是單線程的,因此能夠保證只存在單一的實例。設計
首先要說明的是,雙重檢驗鎖也是一種延遲加載,而且較好的解決了在確保線程安全的時候效率低下的問題。code
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
對比一下最原始的那種線程安全的方法(就是懶漢模式的第二種代碼),那種方法將整個getInstance方法鎖住,那麼每次調用那個方法都要得到鎖,釋放鎖,等待等等...而雙重校驗鎖鎖住了部分的代碼。進入方法若是檢查爲空才進入同步代碼塊,這樣很明顯效率高了不少。
有人疑問爲何instance==null要判斷兩次嗎,那咱們先去掉第二次的判斷。
若是兩個線程一塊兒調用getInstance方法,而且都經過了第一次的判斷instance==null,那麼第一個線程獲取了鎖,而後實例化了instance,而後釋放了鎖,而後第二個線程獲得了線程,而後立刻也實例化了instance,這就尷尬了。單例模式就失敗了。
因此加上第二次判斷後,先進來的線程判斷了一下,哦,爲空,我建立一個,而後建立一個實例以後釋放了鎖,第二個線程進來以後,哎?已經有了,那我就不用建立了,而後釋放了鎖,開開心心的完成了單例模式。blog
懶漢模式須要考慮線程安全,因此咱們多寫了好多的代碼,餓漢模式利用了類加載的特性爲咱們省去了線程安全的考慮,那麼,既能享受類加載確保線程安全帶來的便利,又能延遲加載的方式,就是靜態內部類。Java靜態內部類的特性是,加載的時候不會加載內部靜態類,使用的時候纔會進行加載。而使用到的時候類加載又是線程安全的,這就完美的達到了咱們的預期效果~
public class Singleton { private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } }
JDK1.5提供了一個新的數據類型,枚舉。枚舉的出現提供了一個較爲優雅的方式取代之前大量的static final類型的變量。而這裏,咱們也利用枚舉的特性,實現了單例模式,這種思路是《Effective Java》中第三條最後一段給出的實現方式,有興趣的能夠看看這本書~
代碼簡單到沒法理解:
public enum Singleton { INSTANCE; }
外部調用由原來的Singleton.getInstance變成了Singleton.INSTANCE了。
這裏要注意,原來的class已經換成了關鍵字enum,可是其實無所謂的,看下繼承關係就能知道,其實仍是一個class
並且咱們能夠查看Enum的源碼:
這裏能看到實現了Serializable接口,因此不用考慮序列化的問題(其實序列化反序列化也能致使單例失敗的,可是咱們這裏不過多研究)。對於線程安全,一樣的,加載的時候JVM能確保只加載一個實例。
在單例模式各類設計的方法中,咱們使用到了內部靜態類的特性,使用了枚舉的特性,因此基礎很是重要,單例模式是設計模式之一,而設計模式實際上是對語言特性不足的一面進一步的包裝。吸納基礎,工做學習多加思考,設計模式也就天然而然的可以理解。
另外,好多帖子都說利用枚舉的方式在團隊合做中不常使用,由於須要配合,用了枚舉別人不熟悉。這點我是不一樣意的,若是由於你們不懂不熟悉而放棄了使用很棒的特性,那麼就永遠抱着舊的方式停滯不前,JDK的更新也失去了意義。
仍是但願可以不斷的接觸Java新鮮的想法,在深厚的基礎上迸發不同的思路,而後去解決實際的問題。
以上,若有不妥,還望指正。