一個單例還能寫出花來?

来源:http://www.cnblogs.com/snaildev/archive/2017/09/25/7592207.html
-Advertisement-
Play Games

什麼是單例模式? 從“單例”字面意思上理解為——一個類只有一個實例,所以單例模式也就是保證一個類只有一個實例的一種實現方法罷了。其官方定義為:確保一個類只有一個實例,並提供一個全局訪問點。 為什麼會有單例模式? 從單例模式的定義中我們可以看出——單例模式的使用自然是當我們的系統中某個對象只需要一個實 ...


什麼是單例模式?

從“單例”字面意思上理解為——一個類只有一個實例,所以單例模式也就是保證一個類只有一個實例的一種實現方法罷了。
其官方定義為:確保一個類只有一個實例,並提供一個全局訪問點

為什麼會有單例模式?

從單例模式的定義中我們可以看出——單例模式的使用自然是當我們的系統中某個對象只需要一個實例的情況。

剖析單例模式實現思路

  1. 明確目的:(1)確保一個類只有一個實例;(2)提供一個訪問它的全局訪問點;
  2. 類的實例化基本都是通過關鍵字new的,而定義私有的構造函數就不能在外界通過new創建實例,類實例的創建在類裡面;
  3. 每個線程都有自己的線程棧,定義一個靜態私有變數保存類的實例主要是為了在多線程確保類有一個實例;
  4. 定義一個公有靜態方法是為了公開類的實例;

簡單代碼實現如下:

/// <summary>
    /// 單例模式(確保一個類只有一個實例,並提供一個全局訪問點)
    /// </summary>
    public sealed class Singleton
    {
	    /// <summary>
	    /// 私有靜態變數保存類的唯一實例
	    /// </summary>
	    private static Singleton uniqueInstance;
	    
	    /// <summary>
	    /// 私有構造方法,避免外部 new
	    /// </summary>
	    private Singleton() { }
	    
	    /// <summary>
	    /// 暴露全局訪問點
	    /// </summary>
	    /// <returns></returns>
	    public static Singleton GetInstance()
	    {
		    if (uniqueInstance == null)
		    uniqueInstance = new Singleton();
		    
		    return uniqueInstance;
	    }
    }

上面的單例模式的實現在單線程下確實是完美的,然而在多線程的情況下會得到多個Singleton實例,因為在兩個線程同時運行GetInstance方法時,此時兩個線程判斷(uniqueInstance ==null)這個條件時都返回真,此時兩個線程就都會創建Singleton的實例,這樣就違背了我們單例模式初衷了,既然上面的實現會運行多個線程執行,那我們對於多線程的解決方案自然就是使GetInstance方法在同一時間只運行一個線程運行就好了,也就是我們線程同步的問題了,具體的解決多線程的代碼如下:

/// <summary>
/// 單例模式(確保一個類只有一個實例,並提供一個全局訪問點,線程同步)
/// </summary>
public sealed class Singleton_MultiThread
{
    /// <summary>
    /// 私有靜態變數保存類的唯一實例
    /// </summary>
    private static Singleton_MultiThread uniqueInstance;

    /// <summary>
    /// 鎖,確保線程同步
    /// </summary>
    private static readonly object locker = new object();

    /// <summary>
    /// 私有構造方法,避免外部 new
    /// </summary>
    private Singleton_MultiThread() { }

    /// <summary>
    /// 暴露全局訪問點
    /// </summary>
    /// <returns></returns>
    public static Singleton_MultiThread GetInstance1()
    {
        // 當第一個線程運行到這裡時,此時會對locker對象 "加鎖",
        // 當第二個線程運行該方法時,首先檢測到locker對象為"加鎖"狀態,該線程就會掛起等待第一個線程解鎖
        // lock語句運行完之後(即線程運行完之後)會對該對象"解鎖"
        lock (locker)
        {
            if (uniqueInstance == null)
                uniqueInstance = new Singleton_MultiThread();
        }

        return uniqueInstance;
    }

    /// <summary>
    /// 暴露全局訪問點(雙重鎖定,減小開銷,提升性能)
    /// </summary>
    /// <returns></returns>
    public static Singleton_MultiThread GetInstance2()
    {
        // 當第一個線程運行到這裡時,此時會對locker對象 "加鎖",
        // 當第二個線程運行該方法時,首先檢測到locker對象為"加鎖"狀態,該線程就會掛起等待第一個線程解鎖
        // lock語句運行完之後(即線程運行完之後)會對該對象"解鎖"
        // 雙重鎖定只需要加一句判斷就可以了
        if (uniqueInstance == null)
        {
            lock (locker)
            {
                if (uniqueInstance == null)
                    uniqueInstance = new Singleton_MultiThread();
            }
        }

        return uniqueInstance;
    }
}

上面這種解決方案確實可以解決多線程的問題,但是上面GetInstance1()對於每個線程都會對線程輔助對象locker加鎖之後再判斷實例是否存在,對於這個操作完全沒有必要的,因為當第一個線程創建了該類的實例之後,後面的線程此時只需要直接判斷(uniqueInstance==null)為假,此時完全沒必要對線程輔助對象加鎖之後再去判斷,所以上面的實現方式增加了額外的開銷,損失了性能,為了改進上面實現方式的缺陷,我們只需要在lock語句前面加一句(uniqueInstance==null)的判斷就可以避免鎖所增加的額外開銷,這種實現方式我們就叫它 “雙重鎖定”,可參考GetInstance2()代碼。

單例模式的其他實現方法

靜態初始化
public sealed class Singleton_StaticInit
{
    private static readonly Singleton_StaticInit _instance = new Singleton_StaticInit();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton_StaticInit()
    {
    }

    /// <summary>
    /// Prevents a default instance of the 
    /// <see cref="Singleton_StaticInit"/> class from being created.
    /// </summary>
    private Singleton_StaticInit()
    {
    }

    /// <summary>
    /// Gets the instance.
    /// </summary>
    public static Singleton_StaticInit GetInstance
    {
        get
        {
            return _instance;
        }
    }
}

以上方式實現比之前介紹的方式都要簡單,但它確實是多線程環境下,C#實現的Singleton的一種方式。由於這種靜態初始化的方式是在自己的欄位被引用時才會實例化。

延遲初始化
public sealed class Singleton_LazyInit
{
    private Singleton_LazyInit()
    {
    }

    /// <summary>
    /// Gets the instance.
    /// </summary>
    public static Singleton_LazyInit Instance { get { return Nested._instance; } }

    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton_LazyInit _instance = new Singleton_LazyInit();
    }
}

這裡我們把初始化工作放到Nested類中的一個靜態成員來完成,這樣就實現了延遲初始化。上面了一個嵌套類借鑒了.Net中lambda和匿名函數的實現原理。

Lazy< T > type
/// <summary>
/// .NET 4's Lazy<T> type
/// </summary>
public sealed class Singleton_LazyType
{
    private static readonly Lazy<Singleton_LazyType> lazy =
        new Lazy<Singleton_LazyType>(() => new Singleton_LazyType());

    public static Singleton_LazyType Instance { get { return lazy.Value; } }

    private Singleton_LazyType()
    {
    }
}

這種方式的簡單和性能良好,而且還提供檢查是否已經創建實例的屬性IsValueCreated。

單例模式總結

單例模式的優點:

單例模式(Singleton)會控制其實例對象的數量,從而確保訪問對象的唯一性。
實例控制:單例模式防止其它對象對自己的實例化,確保所有的對象都訪問一個實例。
伸縮性:因為由類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性。

單例模式的缺點:

系統開銷。雖然這個系統開銷看起來很小,但是每次引用這個類實例的時候都要進行實例是否存在的檢查。這個問題可以通過靜態實例來解決。
開發混淆。當使用一個單例模式的對象的時候(特別是定義在類庫中的),開發人員必須要記住不能使用new關鍵字來實例化對象。因為開發者看不到在類庫中的源代碼,所以當他們發現不能實例化一個類的時候會很驚訝。
對象生命周期。單例模式沒有提出對象的銷毀。在提供記憶體管理的開發語言(比如,基於.NetFramework的語言)中,只有單例模式對象自己才能將對象實例銷毀,因為只有它擁有對實例的引用。在各種開發語言中,比如C++,其它類可以銷毀對象實例,但是這麼做將導致單例類內部的指針指向不明。

單例模式的適用性:

使用Singleton模式有一個必要條件:在一個系統要求一個類只有一個實例時才應當使用單例模式。反之,如果一個類可以有幾個實例共存,就不要使用單例模式。
不要使用單例模式存取全局變數。這違背了單例模式的用意,最好放到對應類的靜態成員中。
不要將資料庫連接做成單例,因為一個系統可能會與資料庫有多個連接,並且在有連接池的情況下,應當儘可能及時釋放連接。Singleton模式由於使用靜態成員存儲類實例,所以可能會造成資源無法及時釋放,帶來問題。

參考資料

http://www.cnblogs.com/rush/archive/2011/10/30/2229565.html
http://csharpindepth.com/Articles/General/Singleton.aspx

https://github.com/SnailDev/SnailDev.DesignPattern


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本節內容 1.交互操作 2.變數 3.字元串 4.for迴圈、while迴圈 5.break語句、continue語句 1.交互操作 在cmd下的 開始--》cmd--》cd c:\ --》dir cd d:\=切換到d盤 cd=change directory dir=查看當前路徑下的文件 文件名 ...
  • 靜態代碼塊:最早執行, 類 被載入記憶體時執行,只執行一次。沒有名字、參數和返回值,有關鍵字static。 構造代碼塊:執行時間比靜態代碼塊晚,比構造函數早,和構造函數一樣,只在 對象 初始化的時候運行。沒有名字、參數和返回值。 構造函數:執行時間比構造代碼塊時間晚,也是在 對象 初始化的時候運行。沒 ...
  • 初學者先廣在精,關註代碼背後的實現,關註內功修煉,瞭解實現原理和思想,形成自己完整的技術體系,知識成片之後就容易觸類旁通,進步的速度就會越來越快。最後以我在每一個項目組和開發人員聊天都會說的幾個例子結尾:“少林功夫裡面有功和拳之分,馬步功,石鎖功是功,蛇拳猴拳是拳,你不可能練會了蛇拳猴拳就能打人,你... ...
  • 簡單工廠模式 工廠模式 抽象工廠模式 簡單工廠模式 工廠模式 抽象工廠模式 簡單工廠模式 什麼是簡單工廠模式? 在現實生活中工廠是負責生產產品的,同樣在設計模式中,簡單工廠模式我們也可以理解為負責生產對象的一個類, 我們平常編程中,當使用"new"關鍵字創建一個對象時,此時該類就依賴與這個對象,也就 ...
  • 微服務里一個重要的概念就是服務註冊與發現技術,當你有一個新的服務運行後,我們的服務中心可以感知你,然後把加添加到服務列表裡,然後當你死掉後,會從服務中心把你移除,而你作為一個服務,對其它服務公開的只是服務名稱,而不是最終的服務地址URL,這對於雲平臺,容器化架構來說是非常重要的! 一 安裝單獨的Eu ...
  • 1.類圖 UML類圖是用來描述類、介面、協作及它們之間的關係的圖。用來顯示系統中各個類的靜態結構。 2.類圖的組成元素 類圖由以下六種元素組成:類,介面,泛化關係,關聯關係,依賴關係,實現關係。 3.類圖的繪製 3.1類圖的表示法 類的UML表示為一個長方形垂直分為三個部分:頂部為類的名稱部分,中間 ...
  • Redis是一個key value存儲系統,現在在各種系統中的使用越來越多,大部分情況下是因為其高性能的特性,被當做緩存使用,這裡介紹下Redis經常遇到的使用場景。 Redis特性 一個產品的使用場景肯定是需要根據產品的特性,先列舉一下Redis的特點: 讀寫性能優異 持久化 數據類型豐富 單線程 ...
  • GOF《設計模式》一書中提出了七條設計原則,七原則是一種理想狀態的表達,但實際項目開發中可能會不得不打破這些原則的限制。任何基類可以出現的地方,子類一定可以出現,且必須遵從基類所有規則定義,但反過來說,除了擴展基類,我們又為什麼要違背基類規則定義呢?這一條與開關原則結合起來理解就是,基類遵循關原則,... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...