【設計模式】單例模式 Singleton Parttern

来源:https://www.cnblogs.com/vaiyanzi/archive/2018/08/05/9424419.html
-Advertisement-
Play Games

通常我們在寫程式的時候會碰到一個類只允許在整個系統中只存在一個實例(Instance) 的情況, 比如說我們想做一計數器,統計某些介面調用的次數,通常我們的資料庫連接也是只期望有一個實例。Windows系統的系統任務管理器也是始終只有一個,如果你打開了windows管理器,你再想打開一個那麼他還是同 ...


通常我們在寫程式的時候會碰到一個類只允許在整個系統中只存在一個實例(Instance)  的情況, 比如說我們想做一計數器,統計某些介面調用的次數,通常我們的資料庫連接也是只期望有一個實例。Windows系統的系統任務管理器也是始終只有一個,如果你打開了windows管理器,你再想打開一個那麼他還是同一個界面(同一個實例), 還有比如 做.Net平臺的人都知道,AppDomain 對象,一個系統中也只有一個,所有的類庫都會載入到AppDomain中去運行。只需要一個實例對象的場景,隨處可見,那麼有麽有什麼好的解決方法來應對呢? 有的,那就是 單例模式。

一、單例模式定義

單例模式(Singleton Pattern):確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱為單例類,它提供全局訪問的方法。單例模式是一種對象創建型模式。

二、單例模式結構圖

image

  • Singleton(單例):在單例類的內部實現只生成一個實例,同時它提供一個靜態的GetInstance()工廠方法,讓客戶可以訪問它的唯一實例;為了防止在外部對其實例化,將其構造函數設計為私有(private);在單例類內部定義了一個Singleton類型的靜態對象,作為外部共用的唯一實例。

三、 單例模式典型代碼

public class Singleton
{
    private static Singleton instance;
    private Singleton()
    {
    }
    public static Singleton GetInstance()
    {
        if(instance==null)
        {
            instance=new Singleton();
        }

        return instance;
    }
}

客戶端調用代碼:

static void Main(string[] args)
{
    Singleton singleto = Singleton.GetInstance();
}

在C#中經常將統一訪問點暴露出一個只讀的屬性供客戶端程式使用,這樣代碼就變成了這樣:

public class Singleton
{
    private static Singleton instance;
    private Singleton()
    {
    }
    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }

            return instance;
        }
    }
}

客戶端調用:

static void Main(string[] args)
{
    Singleton singleton = Singleton.GetInstance;
}

四、單例模式實例

1. 懶漢模式

假如我們要做一個程式計數器,一旦程式啟動無論多少個客戶端調用這個 計數器計數的結果始終都是在前一個的基礎上加1,那麼這個計數器類就可以設計成一個單例模式的類。

public class SingletonCounter
{
    private static SingletonCounter instance;
    private static int number=0;
    private SingletonCounter() { }
    public static SingletonCounter Instance
    {
        get
        {
            if (instance == null) instance = new SingletonCounter();

            number++;
            return instance;
        }
    }

    public int GetCounter(){
        return number;
    }
}

客戶端調用:

static void Main(string[] args)
{
    //App A call the counter;
    SingletonCounter singletonA = SingletonCounter.Instance;
    int numberA = singletonA.GetCounter();
    Console.WriteLine("App A call the counter get number was:" + numberA);

    //App B call the counter;
    SingletonCounter singletonB = SingletonCounter.Instance;
    int numberB = singletonA.GetCounter();
    Console.WriteLine("App B call the counter get number was:" + numberB);

    Console.ReadKey();
}

輸出結果:

image

這個實現是線程不安全的,如果有多個線程同時調用,並且又恰恰在計數器初始化的瞬間多個線程同時檢測到了 instance==null為true情況,會怎樣呢?這就是下麵要討論的 “加鎖懶漢模式”

2、加鎖懶漢模式

多個線程同時調用並且同時檢測到 instance == null 為 true的情況,那後果就是會出現多個實例了,那麼就無法保證唯一實例了,解決這個問題就是增加一個對象鎖來確保在創建的過程中只有一個實例。(鎖可以確保鎖住的代碼塊是線程獨占訪問的,如果一個線程占有了這個鎖,其它線程只能等待該線程釋放鎖以後才能繼續訪問)。

public class SingletonCounter
{
    private static SingletonCounter instance;
    private static readonly object locker = new object();
    private static int number = 0;
    private SingletonCounter() { }
    public static SingletonCounter Instance
    {
        get
        {
            lock (locker)
            {
                if (instance == null) instance = new SingletonCounter();

                number++;
                return instance;
            }
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

客戶端調用代碼:

static void Main(string[] args)
{ 
    for (int i = 1; i < 100; i++)
    {
        var task = new Task(() =>
        {
            SingletonCounter singleton = SingletonCounter.Instance;
            int number = singleton.GetCounter();

            Console.WriteLine("App  call  the counter get number was:" +  number);

        });
        task.Start();
    }
    Console.ReadKey();
}

輸出結果:

image

這種模式是線程安全,即使在多線程的情況下仍然可以保持單個實例。那麼這種模式會不會有什麼問題呢?假如系統的訪問量非常大,併發非常高,那麼計數器就會是一個性能瓶頸,因為對鎖會使其它的線程無法訪問。在訪問量不大,併發量不高的系統尚可應付,如果高訪問量,高併發的情況下這樣做肯定是不行的,那麼有什麼辦法改進呢?這就是下麵要討論的“雙檢查加鎖懶漢模式”。

3、雙檢查加鎖懶漢模式

加鎖懶漢模式雖然保證了系統的線程安全,但是卻為系統帶來了新能問題,主要的性能來自鎖帶來開銷,雙檢查就是解決這個鎖帶來的問題,在鎖之前再做一次 instance==null的檢查,如果返回true就直接返回 單例對象了,避開了無謂的鎖, 我們來看下,雙檢查懶漢模式代碼:

public class DoubleCheckLockSingletonCounter
{
    private static DoubleCheckLockSingletonCounter instance;
    private static readonly object locker = new object();
    private static int number = 0;
    private DoubleCheckLockSingletonCounter() { }
    public static DoubleCheckLockSingletonCounter Instance
    {
        get
        {
            if (instance == null)
            {
                lock (locker)
                {
                    if (instance == null)
                    {
                        instance = new DoubleCheckLockSingletonCounter();
                    }
                }
            }
            number++;
            return instance;
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

客戶端調用代碼和“懶漢加鎖模式”相同,輸出結果也相同。

4、餓漢模式

單例模式除了我們上面講的三種懶漢模式外,還有一種叫“餓漢模式”的實現方式,“餓漢模式”直接在Singleton類里實例化了當前類的實例,並且保存在一個靜態對象中,因為是靜態對象,所以在程式啟動的時候就已經實例化好了,後面直接使用,因此不存線上程安全的問題。

下麵是“餓漢模式”的代碼實現:

public class EagerSingletonCounter
{
    private static EagerSingletonCounter instance = new EagerSingletonCounter();

    private static int number = 0;
    private EagerSingletonCounter() { }
    public static EagerSingletonCounter Instance
    {
        get
        {
            number++;
            return instance;
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

 

五、單例模式應用場景

單例模式只有一個角色非常簡單,使用的場景也很明確,就是一個類只需要、且只能需要一個實例的時候使用單例模式。

六、擴展

 

1、”餓漢模式“和”懶漢模式“的比較

”餓漢模式“在程式啟動的時候就已經實例化好了,並且一直駐留在系統中,客戶程式調用非常快,因為它是靜態變數,雖然完美的保證線程的安全,但是如果創建對象的過程很複雜,要占領系統或者網路的一些昂貴的資源,但是在系統中使用的頻率又極低,甚至系統運行起來後都不會去使用該功能,那麼這樣一來,啟動之後就一直占領著系統的資源不釋放,這有些得不償失。

“懶漢模式“ 恰好解決了”餓漢模式“這種占用資源的問題,”懶漢模式”將類的實例化延遲到了運行時,在使用時的第一次調用時才創建出來並一直駐留在系統中,但是為瞭解決線程安全問題, 使用對象鎖也是 影響了系統的性能。這兩種模式各有各的好處,但是又各有其缺點。

有沒有一種折中的方法既可以避免一開始就實例化且一直占領系統資源,又沒有性能問題的Singleton呢? 答案是:有的。

2、第三種選擇

“餓漢模式“類不能實現延遲載入,不管用不用始終占據記憶體;”懶漢式模式“類線程安全控制煩瑣,而且性能受影響。我們用一種折中的方法來解決這個問題,針對主要矛盾, 即:既可以延時載入又不影響性能。

在Singleton的內部創建一個私有的靜態類用於充當單例類的”初始化器“,專門用來創建Singleton的實例:

public class BestPracticeSingletonCounter
{
    private static class SingletonInitializer{
        public static BestPracticeSingletonCounter instance = new BestPracticeSingletonCounter();
    } 
    
    private static int number = 0;
    private BestPracticeSingletonCounter() { }
    public static BestPracticeSingletonCounter Instance
    {
        get
        {
            number++;
            return SingletonInitializer.instance;
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

這種模式兼具了”餓漢“和”懶漢“模式的優點有摒棄了其缺點,可以說是一個完美的實現。


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

-Advertisement-
Play Games
更多相關文章
  • 關於行內元素(補充一點) 行內元素只能容納文本或其他行內元素。(a特殊a裡面可以放塊級元素) 例子: 關於行高tip: 選擇器的嵌套層級不應大於3級,位置靠後的限定條件應儘可能的精確。 屬性定義必須另起一行。 關於行高的測量: css的三大特性(層疊 優先 繼承) a、層疊性:多種css樣式的疊加 ...
  • 使用html5視頻背景 直到現在,仍然不存在一項旨在網頁上顯示視頻的標準。今天,大多數視頻是通過插件(比如 Flash)來顯示的。然而,並非所有瀏覽器都擁有同樣的插件。HTML5 規定了一種通過 video 元素來包含視頻的標準方法。 瀏覽器支持的視頻格式 當前,video 元素支持Ogg,MPEG ...
  • Require.js與Sea.js的區別 相同之處 和 都是模塊載入器,倡導模塊化開發理念,核心價值是讓 JavaScript 的模塊化開發變得簡單自然。 不同之處 兩者的主要區別如下: RequireJS 想成為瀏覽器端的模塊載入器,同時也想成為 Rhino / Node 等環境的模塊載入器。Se ...
  • 場景: 假如有一天,你的在寫一個前端項目,是關於一份點餐商家電話信息表,你啪塔啪塔地寫完了,突然間項目經理跑過來找你,要求你在每一個商家的電話號碼前都添加一個電話符號,來使得電話號碼更直觀和頁面更美觀。這個時候你就糾結了,這不是折磨人嗎?這不是要我在每個電話號碼前都添加一個<img>標簽?這要整到猴 ...
  • [TOC] 前後端如何通信 前段:客戶端 後端: 伺服器端 所謂的全棧,其實是你可以實現客戶端和伺服器端程式的編寫,而且可以實現倆端之間的通信 客戶端和伺服器端是如何通信的? 本地開發(當前項目可以在本地預覽) 部署到伺服器上,讓別人可以通過功能變數名稱或者外網訪問 購買一臺伺服器(阿裡雲獨立主機,虛擬服務 ...
  • 不知不覺接觸前端的時間已經過去半年了,越來越發覺對知識的學習不應該只停留在會用的層面,這在我學jQuery的一段時間後便有這樣的體會。 雖然jQuery只是一個JS的代碼庫,只要會一些JS的基本操作學習一兩天就能很快掌握jQuery的基本語法並熟練使用,但是如果不瞭解jQUery庫背後的實現原理,相 ...
  • 開發者的javascript造詣取決於對【動態】和【非同步】這兩個詞的理解水平。 這一期主要分析各種實際開發中各種複雜的 指向問題。 一. 嚴格模式 嚴格模式是 ES5 中添加的 的另一種運行模式,它可以禁止使用一些語法上不合理的部分,提高編譯和運行速度,但語法要求也更為嚴格,使用 標記開啟。 嚴格模 ...
  • 這幾天項目著急,同時也學到好多以前沒有接觸過的知識。oninput、onchange與onpropertychange事件的區別, 與input輸入框實時檢測 onchange事件只在鍵盤或者滑鼠操作改變對象屬性,value的值發生變化且失去焦點時觸發,用戶js改變value時無法觸發; onkey ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...