主流原始碼管理工具Github
單例模式(Singleton Pattern),顧名思義,就是被單例的物件只能有一個例項存在。單例模式的實現方式是,一個類能返回物件的一個引用(永遠是同一個)和一個獲得該唯一例項的方法(必須是靜態方法)。通過單例模式,我們可以保證系統中只有一個例項,從而在某些特定的場合下達到節約或者控制系統資源的目的。
單例模式類圖
1. 餓漢模式
最常見、最簡單的單例模式寫法之一。顧名思義,“餓漢模式” 就是很 “飢渴”,所以一上來就需要給它新建一個例項。但這種方法有一個明顯的缺點,那就是不管有沒有呼叫過獲得例項的方法(本例中為 getWife()
),每次都會新建一個例項。
// 餓漢模式 public class Wife { // 一開始就新建一個例項 private static final Wife wife = new Wife(); // 預設構造方法 private Wife() {} // 獲得例項的方法 public static Wife getWife() { return wife; } }
2. 懶漢模式
最常見、最簡單的單例模式之二,跟 “餓漢模式” 是 “好基友”。再次顧名思義,“懶漢模式” 就是它很懶,一開始不新建例項,只有當它需要使用的時候,會先判斷例項是否為空,如果為空才會新建一個例項來使用。
// 懶漢模式 public class Wife { //一開始沒有新建例項 private static Wife wife; private Wife() { } // 需要時再新建 public static Wife getWife() { if (wife == null) { wife = new Wife(); } return wife; } }
3. 執行緒安全的懶漢模式
是不是感覺很簡單?但是上面的懶漢模式卻存在一個嚴重的問題。那就是如果有多個執行緒並行呼叫 getWife()
方法的時候,還是會建立多個例項,單例模式就失效了。
Bug 來了,改改改!
簡單,我們在基本的懶漢模式上,把它設為執行緒同步(synchronized
)就好了。synchronized
的作用就是保證在同一時刻最多隻有一個執行緒執行,這樣就避免了多執行緒帶來的問題。關於 synchronized
關鍵字,你可以 點選這裡 瞭解更多。
// 懶漢模式(執行緒安全) public class Wife { private static Wife wife; private Wife() { } // 添加了 synchronized 關鍵字 public static synchronized Wife getWife() { if (wife == null) { wife = new Wife(); } return wife; } }
4. 雙重檢驗鎖(double check)
執行緒安全的懶漢模式解決了多執行緒的問題,看起來完美了。但是它的效率不高,每次呼叫獲得例項的方法 getWife()
時都要進行同步,但是多數情況下並不需要同步操作(例如我的 wife
例項並不為空可以直接使用的時候,就不需要給 getWife()
加同步方法,直接返回 wife
例項就可以了)。所以只需要在第一次新建例項物件的時候,使用同步方法。
不怕,程式猿總是有辦法的。於是,在前面的基礎上,又有了 “雙重檢驗鎖” 的方法。
// 雙重鎖的 getWife() 方法
public static Wife getWife() {
// 第一個檢驗鎖,如果不為空直接返回例項物件,為空才進入下一步
if (wife == null) {
synchronized (Wife.class) {
//第二個檢驗鎖,因為可能有多個執行緒進入到 if 語句內
if (wife == null) {
wife = new Wife();
}
}
}
return wife ;
}
你以為這終於圓滿了?NO...Too young, too naive!
主要問題在於 wife = new Wife()
這句程式碼,因為在 JVM
(Java 虛擬機器)執行這句程式碼的時候,要做好幾件事情,而 JVM
為了優化程式碼,有可能造成做這幾件事情的執行順序是不固定的,從而造成錯誤。(為了不把問題更加複雜化,這裡沒有深入講解在 JVM
中具體是怎麼回事,有興趣的同學可以點選 這裡 自行了解下。)
這個時候,我們需要給例項加一個 volatile
關鍵字,它的作用就是防止編譯器自行優化程式碼。最後,我們的“雙重檢驗鎖”版本終於出爐了。
// 雙重檢驗鎖
public class Wife {
private volatile static Wife wife;
private Wife() { }
public static Wife getWife() {
if (wife == null) {
synchronized(Wife.class) {
if (wife == null) {
wife = new Wife();
}
}
}
return wife;
}
}
5. 靜態內部類
上面的方法,修修補補,實在是太複雜了... 而且 volatile
關鍵字在某些老版本的 JDK 中無法正常工作。咱們得換一種方法,即 “靜態內部類”。這種方式,利用了 JVM 自身的機制來保證執行緒安全,因為 WifeHolder
類是私有的,除了 getWife()
之外沒有其它方式可以訪問例項物件,而且只有在呼叫 getWife()
時才會去真正建立例項物件。(這裡類似於 “懶漢模式”)
// 靜態內部類
public class Wife {
private static class WifeHolder {
private static final Wife wife = new Wife();
}
private Wife() { }
public static Wife getWife() {
return WifeHolder.wife;
}
}
6.列舉
還不懂什麼是列舉的,先 點選這裡 補補課。
如下,程式碼簡直是簡單得不能再簡單了。我們可以通過 Wife.INSTANCE 來訪問例項物件,這比 getWife() 要簡單得多,而且建立列舉預設就是執行緒安全的,還可以防止反序列化帶來的問題。這麼優(niu)雅(bi)的方法,來自於新版 《Effective Java》 這本書。這種方式雖然不常用,但是最為推薦。
// 列舉
public enum Wife {
INSTANCE;
// 自定義的其他任意方法
public void whateverMethod() { }
}
單例模式的應用
當你只需要一個例項物件的時候,就可以考慮使用單例模式。比如在資源共享的情況下,避免由於多個資源操作導致的效能或損耗等就可以使用單例模式。
參考連結--- Java Design Pattern: Singleton