1 介紹:
單例模式是很是簡單、很是基礎的一種建立型設計模式。單例模式是在Java開發(不管是android開發,仍是Java服務器端開發)中常用的一個設計模式。因爲在Java服務器端開發中常用,因此單例模式的實現還涉及到了Java併發編程。在java面試中,經常被要求使用java實現單例模式,所以有必要熟練掌握。本文是本身學習java單例模式的一個總結,參考了網上的不少資料,大部份內容不是原創。在這裏要向參考文獻的做者表示感謝。
本文組織結構以下: 2 介紹單例模式的java實現到底有多少種;3-9分別介紹這些實現方式; 10 提出一些進一步的問題; 11 總結本文。html
2 到底有多少種實現?
"【深刻】java 單例模式"一文列出了5種實現方式,一種爲GOF的、線程不安全的實現方式,剩下4種是線程安全(thread-safe)的實現方式,分別爲:同步方法(synchronized function)方式、雙重檢查加鎖(DCL,double-checked locking)方式、急切加載方式 和內部類方式。
"Java:單例模式的七種寫法"一文列出了7種實現方式:線程不安全方式、同步方法方式、急切加載方式(及其變種)、靜態內部類方式、枚舉方式和雙重校驗鎖方式。 但急切加載方式及其變種應該統一看作是一種實現。
因此本文的結論就是: java單例模式一共有下面將要介紹的5種實現方式。
java
4. 第一種: 線程不安全方式(GOF方式\經典方式) ( 懶加載)
android
這是GOF和HeadFirstDesignPattern中介紹的最經典的一種方式。這種方式在單線程狀況下能夠實現懶加載,但在多線程狀況下會出問題。面試
1 public class Creation_singleton { 2 // private static instance, which is default null 3 private static Creation_singleton instance; 4 // private constructor 5 private Creation_singleton () { 6 } 7 // public get instance function 8 public static Creation_singleton getInstance ( ) { 9 if ( instance == null ) { 10 instance = new Creation_singleton(); 11 } 12 return instance; 13 } 14 }
5. 第二種:簡單加鎖方式 ( synchronized )
編程
由第一種而來,很天然會想到,保證線程安全的方式能夠是使用synchronized關鍵字。這種方式是線程安全,又是懶加載。但使用synchronized 會下降性能。
設計模式
1 public class Creation_singleton { 2 private static Creation_singleton instance; 3 private Creation_singleton () { } 4 public static Synchronized Creation_singleton getInstance ( ) { 5 if ( instance == null ) { 6 instance = new Creation_singleton(); 7 } 8 return instance; 9 } 10 }
咱們必須避免使用synchronization,由於它很慢。----上述論斷在必定程度上已經不是事實了,由於在現代JVM上,若是是無競爭的同步的話,synchronized並不會很慢了。可是當在多線程環境中,多個線程同時競爭getInstance方法時,仍是可能會致使性能降低。安全
之因此說上述論斷不是事實,是與顯式鎖進行比較得出的結論。內置鎖一度被認爲比Lock顯式鎖的性能低不少,但隨着JVM的優化,synchronized關鍵字的性能已經不比Lock低不少了。服務器
6. 急切加載方式 ( 變種 )
多線程
1 class Singleton { 2 //私有,靜態的類自身實例 3 private static Singleton instance = new Singleton(); 4 //私有的構造子(構造器,構造函數,構造方法) 5 private Singleton(){} 6 //公開,靜態的工廠方法 7 public static Singleton getInstance() { 8 return instance; 9 } 10 }
這種方式有一種變種,就是使用靜態初始化塊來初始化單例。
有人說這種方式有個缺點:其實跟全局變量是同樣的: 無論該實例是否是到了實際被使用的時刻,也無論應用程序最終是否是使用該單例,在類加載 時都會建立該單例,因此叫「急切建立」, 可是這樣一來,單例模式的延遲建立的優勢就沒有了。 因此該版本與全局變量的方式並無區別(?yes) static 成員變量只有在類加載的時候初始化一次。類加載是線程安全的。 因此該方法實現的單例是線程安全的。 (static的全局變量也是線程安全的)
可是jvm的類加載也是懶加載的,並非一開始啓動jvm的時候就所有加載全部類。加載這個類,跟建立這個類的實例,兩者的時機,在大部分時候,應該是同時的,也就是說,應該不存在不少跟建立這個類的實例無關的、須要加載這個類的狀況。正是因爲這個緣由,這種方式,在NTS代碼中使用的不少。
7. 雙重檢查加鎖(dcl)方式
在生產環境中能夠看到以下代碼,是雙重檢查加鎖的實現。雙重檢查加鎖是簡單加鎖方式的一種「自做聰明」的改進,其實這種方式在多線程環境下會失敗。
併發
1 private static SocketFactory instance = null; 2 3 private static SocketFactory getInstance() 4 { 5 if (instance == null) 6 { 7 synchronized (SocketFactory.class) 8 { 9 if (instance == null) 10 { 11 instance = new SocketFactory(); 12 } 13 } 14 } 15 return instance; 16 }
雙重檢查加鎖方式也能夠正確地被實現,前提是對上述雙重檢查加鎖方式作出必定的改進。這也讓雙重檢查加鎖方式成爲了最複雜的實現方式。所以這種方式是不被推薦的。 雙重檢查加鎖方式的正確實現由三種:volatile方式、 final方式、 temp instance方式。由於temp instance 方式很是的繁瑣,在本文中就再也不作介紹了,有興趣的讀者能夠去參考文獻中找答案。
在Java5中,DCL其實是可行的,若是你給instance域加上volatile修飾符。例如,若是咱們須要給getInstance()方法傳遞一個database connection的話,那麼如下代碼是可行的:
1 public class MyFactory { 2 private static volatile MyFactory instance; 3 4 public static MyFactory getInstance(Connection conn) 5 throws IOException { 6 if (instance == null) { 7 synchronized (MyFactory.class) { 8 if (instance == null) 9 instance = new MyFactory(conn); 10 } 11 } 12 return instance; 13 } 14 15 private MyFactory(Connection conn) throws IOException { 16 // init factory using the database connection passed in 17 } 18 }
可是須要注意的是,上述代碼僅僅在Java5及其以上版本中才能夠,由於Java5對volatiel關鍵字的語義進行了修正。(Java4及其之前版本中Volatile關鍵字的語義都不是徹底正確的)。當使用Java5獲取一個volatile變量時,獲取行爲具備synchronization同步語義。換句話說,Java5保證了以下的Happen-before規則:對volatile變量的未同步的讀操做必須在寫操做以後才能發生,從而讀線程將會看到MyFactory對象的全部域的正確值。
7.2 把instance域聲明爲final
從java 5 以後纔有的新特性之一是,final 域的語義有了新的變化。這些域的值是在構造函數中被賦值的,JVM會確保這些值在這個對象引用本身以前被提交到主內存。
換句話說,若是其餘線程能夠看到這個對象,那麼它們永遠不可能看到這個對象的final域的未經初始化的值。因此在這種狀況下,咱們將不須要把這個instance的引用聲明爲volatile。
(問題:是否須要把全部域都聲明爲final? )
8. 靜態嵌套類方式
1 public class Creation_singleton_innerClass { 2 private static class SingletonHolder{ 3 private static Creation_singleton_innerClass instance = new Creation_singleton_innerClass(); 4 } 5 private Creation_singleton_innerClass() { } 6 public static Creation_singleton_innerClass getInstance() { 7 return SingletonHolder.instance; 8 } 9 }
該方式與急切建立方式有些相似。 java機制規定,內部類SingletonHolder只有在getInstance()方法第一次調用的時候纔會被加載(實現了lazy), 說明內部類的加載時機跟外部類的加載時機不一樣。 並且其加載過程是線程安全的(實現線程安全)。內部類加載的時候實例化一次instance,之後不會再初始化。
該實現方式能夠有一些小變化:instance能夠加一個final修飾;靜態嵌套類能夠改成內部類。
9. 枚舉方式
1 enum Singleton_enum { 2 INSTANCE; 3 void method () { 4 } 5 }
該方式是《effective java》中推薦的方法。 枚舉類型是在java1.5中引入的,enum能夠看作是一種特殊的class,除了不能繼承。INSTANCE是Singleton_enum類的實例。
上述代碼會被JVM編譯爲:
1 final class MySingleton { 2 final static MySingleton INSTANCE = new MySingleton(); 3 void method () { } 4 }
雖然enum的域是編譯時常量,但他們是對應enum類型的實例,他們僅在對應enum類型首次被引用的時候被生成。當代碼首次運行到訪問INSTANCE處時,類MyStingleton纔會被JVM裝載和初始化。該裝載和初始化過程只初始化static域一次。
10 進一步問題:
我在一次面試中,被問到以下問題:單例模式與static方法的區別是什麼? 當時沒回答上來,如今把答案整理以下:
10.1 單例模式與 static方法的簡單比較
static 方法沒法實現單例;
若是類與其餘類的交互比較複雜,容易形成一些跟初始化有關的、很難調試的bug;
static是急切初始化。
static方法最大的不一樣就是不能做爲參數傳遞給別的方法。單例能夠把這個單例object做爲參數傳遞給其餘方法,並當作普通對象來使用。一個靜態類只容許靜態方法。11 參考文獻:http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.htmlhttp://www.blogjava.net/kenzhh/archive/2015/04/06/357824.htmlhttp://www.raychase.net/257https://en.wikipedia.org/wiki/Singleton_pattern