摘要:如此便可使得這一實現方式能夠同時具備線程安全延遲加載以及節省大量同步判斷資源等優勢,可以說是單例模式的最佳實現了
單例模式(Singleton)是一種使用率非常高的設計模式,其主要目的在于保證某一類在運行期間僅被創建一個實例,并為該實例提供了一個全局訪問方法,通常命名為getInstance()方法。單例模式的本質簡言之即是:
控制實例數目
以Java為例,單例模式通??煞譃?strong>餓漢式和懶漢式兩種常規實現方式
餓漢式單例實現餓漢式顧名思義,就是對類實例(食物?)的需求非常強烈,因此,在裝載該單例類的時候就會創建類實例。如下
public class Singleton { /** * 裝載時即創建類實例,并保存在類變量instance中 * 加上static關鍵詞使得該變量能在getInstance()靜態方法中使用 */ private static Singleton instance = new Singleton(); /** * 私有化構造方法,使外部無法通過構造方法構造除instance外的類實例 * 從而達到單例模式控制類實例數目的目的 */ private Singleton() { } /** * 類實例的全局訪問方法 * 加上static關鍵詞使得外部可以通過類名直接調用該方法獲取類實例 * @return 單例類實例 */ public static Singleton getInstance() { // 由于類實例在類裝載時已被創建并保存在instance中,因此可直接返回 return instance; } }
事實上,在Android開發中,Android Studio提供了一個直接創建單例類的功能(File->new->Singleton),該功能自動生成的單例類正是采用了餓漢式的實現方式
懶漢式單例實現說到懶,我們自然而然會想到拖延癥這一惡習,這一點和懶漢式的單例實現方式相似,這一實現方式會一直等到真正需要使用對象實例的時候再去創建該實例。如下
public class Singleton { /** * 裝載時不創建類實例,但需要利用一個類變量去保存后續創建的類實例 * 添加static關鍵詞使得該變量能在getInstance()靜態方法中使用 */ private static Singleton instance = null; /** * 私有化構造方法,使外部無法通過構造方法構造除instance外的類實例 * 從而達到單例模式控制類實例數目的目的 */ private Singleton() { } /** * 類實例的全局訪問方法 * 添加static關鍵詞使得外部可以通過類名直接調用該方法獲取類實例 * @return 單例類實例 */ public static Singleton getInstance() { // 如果instance未被初始化,則初始化該類實例 if (instance == null) { instance = new Singleton(); } return instance; } }
事實上,雖然我們前面拿拖延癥來與懶漢式做類比,但懶漢式的拖延卻是實際開發中的一種較為常見的節省資源的方式,即延遲加載思想。這一思想的核心在于直到需要使用某些資源或數據時再去加載該資源或獲取該數據,這樣可以盡可能地節省使用前的內存空間
線程安全的懶漢式單例實現不難分析出,當外部多個線程同時想要獲取單例類實例時,上述懶漢式實現方式便很容易導致并發問題。通常有如下幾種改進方式
添加synchronized關鍵詞.... public static synchronized Singleton getInstance() { ....
這種改進方式是最簡單的,但由于外部每次調用getInstance()方法時均需進行判斷,因此該方式也是效率較低的
利用雙重檢查加鎖機制雙重檢查加鎖機制分為如下兩重檢查
在程序每次調用getInstance()方法時先不進行同步,而是在進入該方法后再去檢查類實例是否存在,若不存在則進入接下來的同步代碼塊
進入同步代碼塊后將再次檢查類實例是否存在,若不存在則創建一個新的實例
這樣一來,就只需要在類實例初始化時進行一次同步判斷即可,而非每次調用getInstance()方法時都進行同步判斷,大大節省了時間,具體實現如下
public class Singleton { /** * 裝載時不創建類實例,但需要利用一個類變量去保存后續創建的類實例 * 添加volatile關鍵詞使其不會被本地線程緩存,保證線程能正確處理 * 添加static關鍵詞使得該變量能在getInstance()靜態方法中使用 */ private volatile static Singleton instance = null; /** * 私有化構造方法,使外部無法通過構造方法構造除instance外的類實例 * 從而達到單例模式控制類實例數目的目的 */ private Singleton() { } /** * 類實例的全局訪問方法 * 添加static關鍵詞使得外部可以通過類名直接調用該方法獲取類實例 * @return 單例類實例 */ public static Singleton getInstance() { // 第一重檢查:如果instance未被初始化,則進入同步代碼塊 if (instance == null) { // 同步代碼塊,保證線程安全 synchronized (Singleton.class) { // 第二重檢查:如果instance未被初始化,則初始化該類實例 if (instance == null) { instance = new Singleton(); } } } return instance; } }利用Java緩存思想實現的單例實現
public class Singleton { // 類實例緩存KEY值 private static final String KEY = "CACHE"; // 類實例緩存容器 private static Mapmap = new HashMap<>(); /** * 私有化構造方法,使外部無法通過構造方法構造除instance外的類實例 * 從而達到單例模式控制類實例數目的目的 */ private Singleton() { } /** * 類實例的全局訪問方法 * 添加static關鍵詞使得外部可以通過類名直接調用該方法獲取類實例 * @return 單例類實例 */ public static Singleton getInstance() { // 嘗試從緩存容器中獲取類實例 Singleton instance = map.get(KEY); // 未能獲取類實例,則初始化該實例,并將其緩存至容器中 if (instance == null) { instance = new Singleton(); map.put(KEY, instance); } return instance; } }
上述實現方式暫未考慮線程安全問題。事實上,利用緩存來實現的單例模式其最大的優點在于對單例模式進行擴展。我們自然而然地可以想到這么一種情況,既然在實際開發中經常需要保證某個類只能被創建一個實例,那么,會不會出現保證某個類只能被創建兩個或多個實例這種需求呢?對于這項需求,我們首先可以想到,上述實現方式中所建立的緩存容器是可以存儲多個類實例的,利用這一特點,只需考慮一個問題,即外部調用時到底需要為其返回哪一個實例,便可實現“雙例模式”以及“多例模式”(原諒我為它們取了一些奇怪的名字)了,具體實現如下
public class Singleton { // 可創建的最大類實例數,這里以“雙例模式”為例 private static final int MAX = 2; // 類實例緩存KEY值 private static final String KEY = "CACHE"; // 當前正在使用的實例序號 private static int index = 1; // 類實例緩存容器 private static Map單例模式的最佳實現map = new HashMap<>(); /** * 私有化構造方法,使外部無法通過構造方法構造除instance外的類實例 * 從而達到單例模式控制類實例數目的目的 */ private Singleton() { } /** * 類實例的全局訪問方法 * 添加static關鍵詞使得外部可以通過類名直接調用該方法獲取類實例 * @return 單例類實例 */ public static Singleton getInstance() { // 嘗試從緩存容器中獲取第index個類實例 String key = KEY + index; Singleton instance = map.get(key); // 未能獲取類實例,則初始化該實例,并將其緩存至容器相應index中 if (instance == null) { instance = new Singleton(); map.put(key, instance); } // 這里以最基本的順序調用為例,其他復雜調度方式不加討論,具體調用方式如下 // index++,以在下一次調用中獲取下一個類實例,當達到類實例數上限時,重新獲取第一個類實例 if ((++index) > MAX) { index = 1; } return instance; } }
綜合而言,上述實現方式都或多或少地存在諸如線程不安全、無法做到延遲加載等小缺陷。這里給出一個可以稱得上完美的最佳解決方案
Lazy Initialization Holder Class 模式
這一方案的核心在于Java的類級內部類(即使用static關鍵詞修飾的內部類,否則稱之為對象級內部類)以及多線程缺省同步鎖,先來看看具體實現
public class Singleton { /** * 類級內部類,用于緩存類實例 * 該類將在被調用時才會被裝載,從而實現了延遲加載 * 同時由于instance采用靜態初始化的方式,因此JVM能保證其線程安全性 */ private static class Instance { private static Singleton instance = new Singleton(); } /** * 私有化構造方法,使外部無法通過構造方法構造除instance外的類實例 * 從而達到單例模式控制類實例數目的目的 */ private Singleton() { } /** * 類實例的全局訪問方法 * 添加static關鍵詞使得外部可以通過類名直接調用該方法獲取類實例 * @return 單例類實例 */ public static Singleton getInstance() { return Instance.instance; } }
在前面提到的餓漢式實現方式中,我們利用Java的靜態初始化、借由JVM實現了線程安全,因此這里同樣采用了這種方式。而另一方面,為了避免餓漢式實現中無法進行延遲加載的缺陷,我們構造了一個類級內部類來緩存類實例,由于該類只會在通過getInstance()方法去調用時才會被系統裝載,換言之,只有初次調用getInstance()方法時才會去初始化類實例,因此也實現了延遲加載這一功能。如此便可使得這一實現方式能夠同時具備線程安全、延遲加載以及節省大量同步判斷資源等優勢,可以說是單例模式的最佳實現了
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67780.html
摘要:好,看看大家喜聞樂見的并發場景下,這種簡易的寫法會出現什么問題兩個線程和同時訪問,它們都覺得判斷成立,分別執行了步驟,成功創建出對象但是,我們通篇都在聊單例啊,和的玩法無疑很不單例問題分析出來了,而解決上并不復雜讓線程同步就好。 單例的用處 如果你看過設計模式,肯定會知道單例模式,實際上這是我能默寫出代碼的第一個設計模式,雖然很長一段時間我并不清楚單例具體是做什么用的。這里簡單提一下單...
摘要:在面向對象的語言中,比如,等,單例模式通常是定義類時將構造函數設為,保證對象不能在外部被出來,同時給類定義一個靜態的方法,用來獲取或者創建這個唯一的實例。 萬事開頭難,作為正經歷菜鳥賽季的前端player,已經忘記第一次告訴自己要寫一些東西出來是多久以的事情了。。。如果,你也和我一樣,那就像我一樣,從現在開始,從看到這篇文章開始,打開電腦,敲下你的第一篇文章(或者任何形式的文字)吧。 ...
摘要:但由于這里僅僅是實現一個,因此存儲功能僅通過一個單例類來模擬實現。 本文旨在通過重寫GridView,配合系統彈窗實現仿今日頭條的頻道編輯頁面 注:由于代碼稍長,本文僅列出關鍵部分,完整工程請參見【https://github.com/G9YH/YHChannelEdit】 在開始講解盜版的實現方案前,讓我們先來看看正版與盜版的實際使用效果對比,首先是正版 showImg(https:...
閱讀 25642·2021-09-29 09:41
閱讀 4806·2021-09-10 11:20
閱讀 1928·2021-09-09 09:32
閱讀 1893·2019-08-30 15:44
閱讀 3199·2019-08-29 17:13
閱讀 2815·2019-08-29 14:14
閱讀 2071·2019-08-29 14:11
閱讀 3231·2019-08-29 12:36