設計模式之(一)單例模式

單例模式(Singleton Pattern)是 編程語言中最簡單的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。

? ? 單例模式的意圖在于:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

? ? 何時使用:當您想控制實例數目,節省系統資源的時候。

? ??使用場景:?

? ? ? 1、要求生產唯一序列號。

? ? ? 2、WEB 中的計數器,不用每次刷新都在數據庫里加一次,用單例先緩存起來。?

? ? ? 3、創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。

? ? 優點:?

? ? ? 1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例(比如管理學院首頁頁面緩存)。?

? ? ? 2、避免對資源的多重占用(比如寫文件操作)。?

? ? 缺點:沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。

為了更好便于理解代碼的運行機制,請先耐住性子看完以下的知識點:

涉及知識點:線程鎖

? ? ? ReentrantLock 擁有Synchronized相同的并發性和內存語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定,

? ? ? 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以后,中斷等待,而干別的事情?

設計知識點:靜態成員|靜態方法|靜態構造函數

一、靜態成員(static)

(1) 靜態成員屬于類所有,非靜態成員屬于類的實例所有。

(2) 每創建一個類的實例,都會在內存中為非靜態成員新分配一塊存儲;非靜態成員屬于類所有,為各個類的實例所公用,無論類創建了多少實例,類的靜態成員在內存中只占同一塊區域。

二、靜態方法

(1) 靜態方法屬于類所有,類實例化前即可使用。

(2) 非靜態方法可以訪問類中的任何成員,靜態方法只能訪問類中的靜態成員。因為靜態方法在類實例化前就可以使用,而類中的非靜態變量必須在實例化之后才能分配內存,這樣,靜態方法調用時無法判斷非靜態變量使用的內存地址。所以無法使用.而靜態變量的地址對類來說是固定的,故可以使用。

三、靜態構造函數

(1) 用于對靜態字段、只讀字段等的初始化。

(2) 添加static關鍵字,不能添加訪問修飾符,因為靜態構造函數都是 ?私自有的。

(3) 類的靜態構造函數在給定應用程序域中至多執行一次:只有創建 類的實例或者引用類任何靜態成員才激發靜態構造函數 .

(4) 靜態構造函數是不可繼承的,而且不能被直接調用。

(5) 如果類中包含用來開始執行的 Main 方法,則該類的靜態構造函 數將在調用 Main 方法之前執行。任何帶有初始值設定項的靜態字段,則在執行該類的靜態構造函數時,先要按照文本順序執行那些初始值設定項。

(6) 如果沒有編寫靜態構造函數,而這時類中包含帶有初始值設定的靜態字段,那么編譯器會自動生成默認的靜態構造函數。

(7) 無參數的構造函數可以與靜態構造函數共存。盡管參數列表相同, 但一個屬于類,一個屬于實例,所以不會沖突。 ?MSDN對靜態構造函數的解釋: ? 靜態構造函數用于初始化任何靜態數據,或用于執行僅需執行一次的特定操作。在創建第一個實例或引用任何靜態成員之前,將自動調用靜態構造函數。

靜態構造函數具有以下特點:

靜態構造函數既沒有訪問修飾符,也沒有參數。

如何使用:

? ? 第一步:?創建一個 Singleton 類。

publicclassSingleObject

{

? ? //創建 SingleObject 的一個對象

? ? privatestaticSingleObjectinstance=newSingleObject();

? ? //讓構造函數為 private,這樣該類就不會被實例化

? ? privateSingleObject(){}

? ? //獲取唯一可用的對象

? ? publicstaticSingleObjectgetInstance()

? ? {

? ? ? ? returninstance;

? ? }

? ? publicvoidshowMessage()

? ? {

? ? ? ? System.out.println("Hello World!");

? ? }

}

? ? 第二步:從 singleton 類獲取唯一的對象。

publicclassSingletonPatternDemo

{

? ? publicstaticvoidmain(String[]args)

? ? {

? ? ? ? //不合法的構造函數

? ? ? ? //編譯時錯誤:構造函數 SingleObject() 是不可見的

? ? ? ?//SingleObject object = new SingleObject();

? ? ? ? //獲取唯一可用的對象

? ? ? ? SingleObjectobject=SingleObject.getInstance();

? ? ? ? //顯示消息object.showMessage();

? ? }

}

? ? 第三部:驗證輸出。

? ? Hello World!

單例模式的幾種實現方式

單例模式的實現有多種方式,如下所示:

1、懶漢式,線程不安全

是否 Lazy 初始化:

是否多線程安全:

實現難度:

描述:這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。

這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。

代碼實例:

public class Singleton

?{

? ? private static Singleton instance;

? ? private Singleton (){}

? ? public static Singleton getInstance() {

? ? if (instance == null)?

? ? {

? ? ? ? instance = new Singleton();

? ? }

? ? return instance;

? ? }

}

接下來介紹的幾種實現方式都支持多線程,但是在性能上有所差異。

2、懶漢式,線程安全

是否 Lazy 初始化:

是否多線程安全:

實現難度:

描述:這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。

優點:第一次調用才初始化,避免內存浪費。

缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。

getInstance() 的性能對應用程序不是很關鍵(該方法使用不太頻繁)。

涉及知識點:線程鎖


代碼實例:

public class Singleton

?{

? ? private static Singleton instance;

? ? private Singleton (){}

? ? public static synchronized Singleton getInstance() {

? ? if (instance == null)

? ? {

? ? ? ? instance = new Singleton();

? ? }

? ? return instance;

? ? }

}

3、餓漢式

是否 Lazy 初始化:

是否多線程安全:

實現難度:

描述:這種方式比較常用,但容易產生垃圾對象。

優點:沒有加鎖,執行效率會提高。

缺點:類加載時就初始化,浪費內存。

它基于 classloder 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

代碼實例:

public class Singleton

?{

? ? private static Singleton instance = new Singleton();

? ? private Singleton (){}

? ? public static Singleton getInstance()

? ? {

? ? ? ? return instance;

? ? }

}

4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多線程安全:

實現難度:較復雜

描述:這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。

getInstance() 的性能對應用程序很關鍵。

代碼實例:

public class Singleton

?{

? ? private volatile static Singleton singleton;

? ? private Singleton (){}

? ? public static Singleton getSingleton()

? ? {

? ? ? ? if (singleton == null)

? ? ? ? {

? ? ? ? ? ? synchronized (Singleton.class)

? ? ? ? ? ? ?{

? ? ? ? ? ? ? ? ? ?if (singleton == null)

? ? ? ? ? ? ? ? ? ?{

? ? ? ? ? ? ? ? ? ? ? singleton = new Singleton();

? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ?}

? ? ? ? }

? ? return singleton;

? ?}

}

5、登記式/靜態內部類

是否 Lazy 初始化:

是否多線程安全:

實現難度:一般

描述:這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。

這種方式同樣利用了 classloder 機制來保證初始化 instance 時只有一個線程,它跟第 3 種方式不同的是:第 3 種方式只要 Singleton 類被裝載了,那么 instance 就會被實例化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有顯示通過調用 getInstance 方法時,才會顯示裝載 SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化 instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理。

代碼實例:

public class Singleton

?{

? ? private static class SingletonHolder?

? ? {

? ? ? ? private static final Singleton INSTANCE = new Singleton();

? ? }

? ? private Singleton (){}

? ? public static final Singleton getInstance() {

? ? return SingletonHolder.INSTANCE;

? ? }

}

6、枚舉

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多線程安全:

實現難度:

描述:這種實現方式還沒有被廣泛采用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。

這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。

不能通過 reflection attack 來調用私有構造方法。

代碼實例:

public enum Singleton?

{

? ? INSTANCE;

? ? public void whateverMethod()

? ? {

? ? }

}

經驗之談:一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實現 lazy loading 效果時,才會使用第 5 種登記方式。如果涉及到反序列化創建對象時,可以嘗試使用第 6 種枚舉方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容