:smile: 按照 001 篇講的,之後的每一個模式都將按照:模式名稱、問題、解決方案以及效果這幾個主要的要素研究。
java
開始吧git
中文:單例模式
English: Singleton Pattern
含義:單例對象的類必須保證只有一個實例存在。github
當咱們須要統一管理資源,共享資源的時候就須要使用單例模式。保障數據庫
場景:設計模式
請帶着問題找答案:stuck_out_tongue_winking_eye:。安全
來個UML圖先多線程
科普一下:什麼叫懶漢模式,什麼叫餓漢模式函數
- 懶漢模式 --> 特色是懶,不用的時候我就睡覺(不實例化)。
- 餓漢模式 --> 特色是餓,一上來我就要吃(實例化)。
// 懶漢,你不調用getInstance() 就不會實例化
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}複製代碼
解析:這是一個最簡單的實現方法。經過 getInstance()
獲取 Singleton
這個類的實例。instance == null
的狀況下 new 一個實例。不爲空就返回這個實例。能夠說,這個方法最適合教學,一眼就能看的很明白什麼是單例。
BUT , 想想,我是否是在外面也能 經過 new Singleton()
來建立一個實例 ?那都是多個實例了。還怎麼單例?
So ,咱們要開始進階了。脫離學生手法,開始進階。post
// 懶漢,你不調用getInstance() 就不會實例化
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// add 1.1 begin (將構造方法私有化)
private Singleton() {
}
// add 1.1 end
}複製代碼
通過 v1.1 版本的改造。發如今外面再也 new 不出來 Singleton 的實例。:stuck_out_tongue: 我學會了?給你個眼神:smirk:
咱們再想:兩個線程幾乎同時調用 getInstance()
第一個進入的線程判斷 instance
爲空,開始走這一行 instance = new Singleton();
。,注意,是開始走這一行,並未完成實例化! 此時第二個線程也走到 if (instance == null)
此時判斷也爲空。那麼 這兩個線程都會獲得一個新的實例,那麼就產生兩個實例。那麼還怎麼單例?性能
以上爲懶漢模式 -- (線程不安全)
爲了解決 上面那個方法在多線程下使用不安全的問題。咱們再次進階。
此次吊了�這從線程安全了!
我反手就給你一段代碼
// 懶漢,你不調用getInstance() 就不會實例化
public class Singleton {
private static Singleton instance;
// modif synchronized
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// add 1.1 begin (將構造方法私有化)
private Singleton() {
}
// add 1.1 end
}複製代碼
看,加一個synchronized
同步,來一個要等着。等裏面的代碼執行完了,第二個線程再進去。第二個再進去的時候,instance
就不是空了,就又能單例了。��
Too young。這樣搞的話,咱們每次進來均可能要同步一下。多數時候咱們並非兩個或多個線程同時來訪問,咱們並不須要去同步。這樣作其實形成了沒必要要的開銷。有木有更好的方法呢?
我反手又是一串代碼(關鍵代碼)
只貼上關鍵代碼。纔好
public static Singleton getInstance() {
if (instance == null) { //Single Checked
synchronized (Singleton.class) {
if (instance == null) { //Double Checked
instance = new Singleton();
}
}
}
return instance ;
}複製代碼
看到沒,你想到這樣搞了嗎? 先檢測再同步。Single Checked
足以應付多數檢測。當一個以上的線程同時訪問時就用上了Double Checked
防止多線程下重複建立實例。 �即便咱們這麼想到這麼吊的方法,仍是不行。。。爲啥?
爲啥? instance = new Singleton()
這個不是原子操做。當咱們 new 的時候 JVM 大概作了這些事:
instance
分配內存Singleton
的構造函數,來初始化成員變量instance
對象指向分配的內存空間(執行完這步 instance 就不爲 null 了) 可是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序多是 1-2-3 也多是 1-3-2。若是是後者,則在 3 執行完畢、2 未執行以前,被線程二搶佔了,這時 instance 已是非 null 了(但卻沒有初始化),因此線程二會直接返回 instance,而後使用,而後瓜熟蒂落地報錯。
解決方法也很簡單:加上 volatile
就能夠了。使用 volatile 的主要緣由是其一個特性:禁止指令重排序優化
private volatile static Singleton instance; //聲明成 volatile複製代碼
注意: 在 Java 5 之前的版本使用了 volatile 的雙檢鎖仍是有問題的。其緣由是 Java 5 之前的 JMM (Java 內存模型)是存在缺陷的,即時將變量聲明成 volatile 也不能徹底避免重排序,主要是 volatile 變量先後的代碼仍然存在重排序問題。這個 volatile 屏蔽重排序的問題在 Java 5 中才得以修復,因此在這以後才能夠放心使用 volatile。
註解
原子操做: 若是這個操做所處的層(layer)的更高層不能發現其內部實現與結構,那麼這個操做是一個原子(atomic)操做。
原子操做能夠是一個步驟,也能夠是多個操做步驟,可是其順序不能夠被打亂,也不能夠被切割而只執行其中的一部分。
將整個操做視做一個總體是原子性的核心特徵。
這個方法解決了上面全部的不安全因素,可是!在 Java 5 之前的版本上跑卻仍是會有問題,因此,這個也不是最好的方法。。。
�
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}複製代碼
是這樣的,由於單例的實例 instance
被聲明稱 static
和 final
了,在第一次加載類到內存中就會被實例化。因此建立實例自己是線程安全的。一上來就加載,因此是餓漢。
缺點:太着急加載。有時候咱們想加點料也不給機會。有時候咱們建立實例須要依賴參數或者配置文件。這樣就不能使用餓漢模式。�
怎麼辦?
先瞅代碼
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance(int a) {
return SingletonHolder.INSTANCE;
}
}複製代碼
因爲 SingletonHolder
是私有的,除了 getInstance()
以外沒有辦法訪問它,所以它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。能完美應對多數場景。若是你以爲寫着麻煩,其實還有這一種很簡單的寫法。好像不太經常使用。就是下面這個枚舉
《Effective Java》:單元素的枚舉類型已經成爲實現Singleton的最佳方法
public enum Singleton {
//理解爲 public static final Singleton INSTANCE;
INSTANCE;
}複製代碼
實例化:Singleton.INSTANCE
就是這麼簡單。可是單例這樣用的人我以爲仍是很少。猜測是你們對枚舉了解不太多吧。若是看到枚舉這個方式一臉懵B,就看看枚舉相關的知識。反正我一開始也是一臉**
以上介紹了:
幾種方法不重要。最重要的知道什麼是單例模式,爲啥用單例。甚至不在代碼中使用,工做、生活、學習、遊戲中充滿單例思想。掌握這個思想以及解決辦法,生活會很精彩��
再說一下:《Effective Java》推薦 DLC 和枚舉,那些明顯有問題的寫法就不要用了。那些寫法都是用來教學理解單例的。
更新中:
1. 什麼是設計模式
2. 單例模式
3. 簡單工廠模式
GitHub彙總:這裏老是最新的
看完給個star鼓勵一下