摘要:總結我們主要介紹到了以下幾種方式實現單例模式餓漢方式線程安全懶漢式非線程安全和關鍵字線程安全版本懶漢式雙重檢查加鎖版本枚舉方式參考設計模式中文版第二版設計模式深入理解單例模式我是一個以架構師為年之內目標的小小白。
初遇設計模式在上個寒假,當時把每個設計模式過了一遍,對設計模式有了一個最初級的了解。這個學期借了幾本設計模式的書籍看,聽了老師的設計模式課,對設計模式算是有個更進一步的認識。后面可能會不定期更新一下自己對于設計模式的理解。每個設計模式看似很簡單,實則想要在一個完整的系統中應用還是非常非常難的。然后我的水品也非常非常有限,代碼量也不是很多,只能通過閱讀書籍、思考別人的編碼經驗以及結合自己的編碼過程中遇到的問題來總結。
怎么用->怎么用才好->怎么與其他模式結合使用,我想這是每個開發人員都需要逾越的一道鴻溝。
本文主要內容 1 單例模式簡介 1.1 定義保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
1.2 為什么要用單例模式呢?在我們的系統中,有一些對象其實我們只需要一個,比如說:線程池、緩存、對話框、注冊表、日志對象、充當打印機、顯卡等設備驅動程序的對象。事實上,這一類對象只能有一個實例,如果制造出多個實例就可能會導致一些問題的產生,比如:程序的行為異常、資源使用過量、或者不一致性的結果。
簡單來說使用單例模式可以帶來下面幾個好處:對于頻繁使用的對象,可以省略創建對象所花費的時間,這對于那些重量級對象而言,是非常可觀的一筆系統開銷;
由于 new 操作的次數減少,因而對系統內存的使用頻率也會降低,這將減輕 GC 壓力,縮短 GC 停頓時間。
1.3 為什么不使用全局變量確保一個類只有一個實例呢?我們知道全局變量分為靜態變量和實例變量,靜態變量也可以保證該類的實例只存在一個。
只要程序加載了類的字節碼,不用創建任何實例對象,靜態變量就會被分配空間,靜態變量就可以被使用了。
但是,如果說這個對象非常消耗資源,而且程序某次的執行中一直沒用,這樣就造成了資源的浪費。利用單例模式的話,我們就可以實現在需要使用時才創建對象,這樣就避免了不必要的資源浪費。 不僅僅是因為這個原因,在程序中我們要盡量避免全局變量的使用,大量使用全局變量給程序的調試、維護等帶來困難。
2 單例的模式的實現 通常單例模式在Java語言中,有兩種構建方式:餓漢方式。指全局的單例實例在類裝載時構建
懶漢方式。指全局的單例實例在第一次被使用時構建。
不管是那種創建方式,它們通常都存在下面幾點相似處:
單例類必須要有一個 private 訪問級別的構造函數,只有這樣,才能確保單例不會在系統中的其他代碼內被實例化;
instance 成員變量和 uniqueInstance 方法必須是 static 的。
2.1 餓漢方式(線程安全)public class Singleton { //在靜態初始化器中創建單例實例,這段代碼保證了線程安全 private static Singleton uniqueInstance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return uniqueInstance; } }
所謂 “餓漢方式” 就是說JVM在加載這個類時就馬上創建此唯一的單例實例,不管你用不用,先創建了再說,如果一直沒有被使用,便浪費了空間,典型的空間換時間,每次調用的時候,就不需要再判斷,節省了運行時間。
## 2.2 懶漢式(非線程安全和synchronized關鍵字線程安全版本 )
public class Singleton { private static Singleton uniqueInstance; private Singleton (){ } //沒有加入synchronized關鍵字的版本是線程不安全的 public static Singleton getInstance() { //判斷當前單例是否已經存在,若存在則返回,不存在則再建立單例 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
所謂 “餓漢方式” 就是說單例實例在第一次被使用時構建,而不是在JVM在加載這個類時就馬上創建此唯一的單例實例。
但是上面這種方式很明顯是線程不安全的,如果多個線程同時訪問getInstance()方法時就會出現問題。如果想要保證線程安全,一種比較常見的方式就是在getInstance() 方法前加上synchronized關鍵字,如下:
public static synchronized Singleton getInstance() { if (instance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; }
我們知道synchronized關鍵字偏重量級鎖。雖然在JavaSE1.6之后synchronized關鍵字進行了主要包括:為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其它各種優化之后執行效率有了顯著提升。
但是在程序中每次使用getInstance() 都要經過synchronized加鎖這一層,這難免會增加getInstance()的方法的時間消費,而且還可能會發生阻塞。我們下面介紹到的 雙重檢查加鎖版本 就是為了解決這個問題而存在的。
2.3 懶漢式(雙重檢查加鎖版本)利用雙重檢查加鎖(double-checked locking),首先檢查是否實例已經創建,如果尚未創建,“才”進行同步。這樣以來,只有一次同步,這正是我們想要的效果。
public class Singleton { //volatile保證,當uniqueInstance變量被初始化成Singleton實例時,多個線程可以正確處理uniqueInstance變量 private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getInstance() { //檢查實例,如果不存在,就進入同步代碼塊 if (uniqueInstance == null) { //只有第一次才徹底執行這里的代碼 synchronized(Singleton.class) { //進入同步代碼塊后,再檢查一次,如果仍是null,才創建實例 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
很明顯,這種方式相比于使用synchronized關鍵字的方法,可以大大減少getInstance() 的時間消費。
我們上面使用到了volatile關鍵字來保證數據的可見性,關于volatile關鍵字的內容可以看我的這篇文章:
《Java多線程學習(三)volatile關鍵字》: https://blog.csdn.net/qq_34337272/article/details/79680771
注意: 雙重檢查加鎖版本不適用于1.4及更早版本的Java。2.4 其他方式(枚舉)
1.4及更早版本的Java中,許多JVM對于volatile關鍵字的實現會導致雙重檢查加鎖的失效。
除了上面說的幾種創建方式之外,還有挺多種其他的創建方式這里稍微多提一點使用枚舉的方式,其他創建方式我們就不管了,沒有什么實質性的作用。
枚舉實現單例的優點就是簡單,但是大部分應用開發很少用枚舉,可讀性并不是很高。個人感覺懶漢式(雙重檢查加鎖版本)還是使用挺多的,這種方式的可讀性也比較好。
public enum Singleton { //定義一個枚舉的元素,它就是 Singleton 的一個實例 INSTANCE; public void doSomeThing() { System.out.println("枚舉方法實現單例"); } }
使用方法:
public class ESTest { public static void main(String[] args) { Singleton singleton = Singleton.INSTANCE; singleton.doSomeThing();//output:枚舉方法實現單例 } }
《Effective Java 中文版 第二版》
這種方法在功能上與公有域方法相近,但是它更加簡潔,無償提供了序列化機制,絕對防止多次實例化,即使是在面對復雜序列化或者反射攻擊的時候。雖然這種方法還沒有廣泛采用,但是單元素的枚舉類型已經成為實現Singleton的最佳方法。 —-《Effective Java 中文版 第二版》
《Java與模式》
《Java與模式》中,作者這樣寫道,使用枚舉來實現單實例控制會更加簡潔,而且無償地提供了序列化機制,并由JVM從根本上提供保障,絕對防止多次實例化,是更簡潔、高效、安全的實現單例的方式。2.5 總結
我們主要介紹到了以下幾種方式實現單例模式:
餓漢方式(線程安全)
懶漢式(非線程安全和synchronized關鍵字線程安全版本)
懶漢式(雙重檢查加鎖版本)
枚舉方式
參考:
《Head First 設計模式》
《Effective Java 中文版 第二版》
【Java】設計模式:深入理解單例模式
我是Snailclimb,一個以架構師為5年之內目標的小小白。
歡迎關注我的微信公眾號:"Java面試通關手冊"(一個有溫度的微信公眾號,期待與你共同進步~~~堅持原創,分享美文,分享各種Java學習資源):
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69511.html
摘要:單例是應用開發中一種設計模式,主要應用場景為當且僅當系統中只能保留一個對象時使用。本文提出中可以在生產環境中使用的單例設計模式。在的一書中給出了三種單例設計模式采用靜態變量這種寫法使用了私有的構造方法。 單例是應用開發中一種設計模式,主要應用場景為:當且僅當系統中只能保留一個對象時使用。本文提出4中可以在生產環境中使用的單例設計模式。推薦使用enum的方式。 應用場景 例如一下應用場景...
摘要:一基礎接口的意義百度規范擴展回調抽象類的意義想不想通過一線互聯網公司面試文檔整理為電子書掘金簡介谷歌求職記我花了八個月準備谷歌面試掘金原文鏈接翻譯者 【面試寶典】從對象深入分析 Java 中實例變量和類變量的區別 - 掘金原創文章,轉載請務必保留原出處為:http://www.54tianzhisheng.cn/... , 歡迎訪問我的站點,閱讀更多有深度的文章。 實例變量 和 類變量...
摘要:在設計模式中,所有的設計模式都遵循這一原則。其實就是說在應用程序中,所有的類如果使用或依賴于其他的類,則應該依賴這些其他類的抽象類,而不是這些其他類的具體類。使用設計模式是為了可重用代碼讓代碼更容易被他人理解保證代碼可靠性。 這是劉意老師的JAVA基礎教程的筆記講的賊好,附上傳送門 傳智風清揚-超全面的Java基礎 一、面向對象思想設計原則 1.單一職責原則 其實就是開發人員經常說的高...
閱讀 2493·2021-11-15 18:14
閱讀 1718·2021-10-14 09:42
閱讀 3751·2021-10-11 10:58
閱讀 3953·2021-10-09 09:44
閱讀 2418·2021-09-26 09:55
閱讀 2440·2021-09-24 10:38
閱讀 2029·2021-09-04 16:48
閱讀 3273·2021-09-02 15:21