c#中單例模式詳解

来源:https://www.cnblogs.com/mingnianjiehunba/archive/2023/11/02/17669212.html
-Advertisement-
Play Games

OpenKey.Cloud 作為 ChatGPT 生態圈內的重要基礎設施,提供官方 API 的轉發,長久以來一直保持著高穩定性,這是如何做到的?今天就來揭秘 OpenKey 系統的詳細架構圖。 ...


基礎介紹:

 確保一個類只有一個實例,並提供一個全局訪問點。

  適用於需要頻繁實例化然後銷毀的對象,創建對象消耗資源過多,但又經常用到的對象,頻繁訪問資料庫或文件的對象。

  其本質就是保證在整個應用程式生命周期中,任何一個時刻,單例類的實例都只存在一個

  • 特性和功能:確保一個類只有一個實例,並提供一個全局訪問點。
  • 使用環境:當類只需要一個實例,且易於訪問,且實例應在整個應用程式中共用時。
  • 註意事項:需要註意線程安全問題。
  • 優點:可以確保一個類只有一個實例,減少了記憶體開銷。
  • 缺點:沒有介面,擴展困難。  

應用場景:

  單例模式通常適用於在整個應用程式中只需要一個實例化對象的場景,以確保資源的高效利用和應用程式的穩定性。(共用資源)

  資源共用的情況下,避免由於資源操作時導致的性能或損耗等。

  控制資源的情況下,方便資源之間的互相通信。如線程池等。

  • 日誌系統:在應用程式中,通常只需要一個日誌系統,以避免在多個地方創建多個日誌對象。這一般是由於共用的日誌文件一直處於打開狀態,所以只能有一個實例去操作,否則內容不好追加也有可能造成資源占用加劇資源消耗。
  • 資料庫連接池:在應用程式中,資料庫連接池是一個非常重要的資源,單例模式可以確保在應用程式中只有一個資料庫連接池實例,避免資源浪費。主要是節省打開或者關閉資料庫連接所引起的效率損耗,因為何用單例模式來維護,就可以大大降低這種損耗。
  • 配置文件管理器:在應用程式中,通常只需要一個配置文件管理器來管理應用程式的配置文件,單例模式可以確保在整個應用程式中只有一個配置文件管理器實例。這個是由於配置文件是共用的資源。
  • 緩存系統:在應用程式中,緩存系統是一個重要的組件,單例模式可以確保在整個應用程式中只有一個緩存實例,以提高應用程式的性能。
  • 網站線上人數統計:其實就是全局計數器,也就是說所有用戶在相同的時刻獲取到的線上人數數量都是一致的。
  • GUI組件:在圖形用戶界面(GUI)開發中,單例模式可以確保在整個應用程式中只有一個GUI組件實例,以確保用戶界面的一致性和穩定性。

創建方式:

  餓漢式:類載入就會導致該單實例對象被創建。(靜態變數方式、靜態代碼塊方式)

  懶漢式:類載入不會導致該單實例對象被創建,而是首次使用該對象時才會創建。(線程不安全型、線程安全型、雙重檢查鎖)

 

  1. 懶漢式---非線程安全型

     1 public class Singleton
     2     {
     3         //定義一個私有的靜態全局變數來保存該類的唯一實例
     4         private static Singleton singleton;
     5 
     6         /// <summary>
     7         /// 構造函數
     8         /// </summary>
     9         private Singleton()
    10         {
    11             //必須是私有的構造函數,這樣就可以保證該類無法通過new來創建該類的實例。
    12             //想要使用該類只能通過唯一訪問點GetInstance()。
    13         }
    14 
    15         /// <summary>
    16         /// 全局訪問點
    17         /// 設置為靜態方法則可在外邊無需創建該類的實例就可調用該方法
    18         /// </summary>
    19         /// <returns></returns>
    20         public static Singleton GetInstance()
    21         {
    22             if (singleton == null)
    23             {
    24                 singleton = new Singleton();
    25             }
    26             return singleton;
    27         }
    28     }

    上面的代碼中,由於構造函數被設置為 private 了,無法再在 Singleton 類的外部使用 new 來實例化一個實例,只能通過訪問 GetInstance()來訪問 Singleton 類。

    GetInstance()通過如下方式保證該 Singleton 只存在一個實例:

    首先這個 Singleton 類會在在第一次調用 GetInstance()時創建一個實例(第24行),並將這個實例的引用封裝在自身類中的靜態全局變數singleton(第4行)

    然後以後調用 GetInstance()時就會判斷這個 Singleton 是否存在一個實例了(第22行),如果存在,則不會再創建實例。

    這樣就實現了懶載入的效果。但是,如果是多線程環境,會出現線程安全問題。

    比如多個線程同時執行GetInstance()方法時都走到了第22行,這個時候一個線程進入 if 判斷語句後但還沒有實例化 Singleton 時,第二個線程到達,此時 singleton 還是為 null

    如此會造成多個線程都會進入 if 執行代碼塊中即都會執行第24行,這樣的話,就會創建多個實例違背了單里模式,因此引出了實例2線程安全型。

     

  2. 懶漢式---線程安全型

     1 public class Singleton
     2     {
     3         //定義一個私有的靜態全局變數來保存該類的唯一實例
     4         private static Singleton singleton;
     5 
     6         //線程鎖
     7         private static readonly object _Object = new object();
     8 
     9         /// <summary>
    10         /// 構造函數
    11         /// </summary>
    12         private Singleton()
    13         {
    14             //必須是私有的構造函數,這樣就可以保證該類無法通過new來創建該類的實例。
    15             //想要使用該類只能通過唯一訪問點GetInstance()。
    16         }
    17 
    18         /// <summary>
    19         /// 全局訪問點
    20         /// 設置為靜態方法則可在外邊無需創建該類的實例就可調用該方法
    21         /// </summary>
    22         /// <returns></returns>
    23         public static Singleton GetInstance()
    24         {
    25             lock (_Object)
    26             {
    27                 if (singleton == null)
    28                 {
    29                     singleton = new Singleton();
    30                 }
    31             }
    32             return singleton;
    33         }
    34     }

    相比實例1中可以看到在類中有定義了一個靜態的只讀對象  _Object(第7行),該對象主要是提供給lock 關鍵字使用。

    lock關鍵字參數必須為基於引用類型的對象,該對象用來定義鎖的範圍。

    當多個線程同時進入GetInstance()方法時,由於存在鎖機制,當一個線程進入lock代碼塊時,其餘線程會在lock語句的外部等待。

    當第一個線程執行完第29行創建對象實例後,便會退出鎖定區域,這個時候singleton變數已經不為null了。

    所以餘下線程再次進入lock代碼塊時,由於第27行的原因則不會再次創建對象的實例。

    但這裡就涉及一個性能問題了,每一次有線程進入 GetInstance()時,均會執行鎖定操作來實現線程同步,這是非常耗費性能的。

    解決這個問題也很簡單,進行雙重檢查鎖定判斷即實例3。

     

  3. 懶漢式---雙重檢查鎖

     1 public class Singleton
     2     {
     3         //定義一個私有的靜態全局變數來保存該類的唯一實例
     4         private static Singleton singleton;
     5 
     6         //線程鎖
     7         private static readonly object _Object = new object();
     8 
     9         /// <summary>
    10         /// 構造函數
    11         /// </summary>
    12         private Singleton()
    13         {
    14             //必須是私有的構造函數,這樣就可以保證該類無法通過new來創建該類的實例。
    15             //想要使用該類只能通過唯一訪問點GetInstance()。
    16         }
    17 
    18         /// <summary>
    19         /// 全局訪問點
    20         /// 設置為靜態方法則可在外邊無需創建該類的實例就可調用該方法
    21         /// </summary>
    22         /// <returns></returns>
    23         public static Singleton GetInstance()
    24         {
    25             if (singleton == null)//第一重
    26             {
    27                 lock (_Object)
    28                 {
    29                     if (singleton == null)//第二重
    30                     {
    31                         singleton = new Singleton();
    32                     }
    33                 }
    34             }
    35             return singleton;
    36         }
    37     }

    相比實例2來看,只是增加了第25行。

    在多線程中,當第一個線程創建完對象的實例後,singleton變數已經不為null了。之後再訪問GetInstance()方法時,將不會再進行lock等待。

    如果沒有這行的情況下,每次多線程同時進入GetInstance()方法時,多餘的線程都會進入lock進行等待。這是非常耗費性能的。

    相比調用GetInstance()方法來作為全局訪問點還有另外一種寫法:

     1  public class Singleton
     2     {
     3         private static Singleton instance;
     4 
     5         private Singleton() { }
     6 
     7         public static Singleton Instance
     8         {
     9             get
    10             {
    11                 if (instance == null)
    12                 {
    13                     instance = new Singleton();
    14                 }
    15                 return instance;
    16             }
    17         }
    18     }

    前三個實例在客戶端調用:Singleton singletonOne = Singleton.GetInstance();

    後一種則可以直接:Singleton.Instance進行使用。

     

  4. 餓漢式

     1 public sealed class Singleton
     2     {
     3         //定義一個私有靜態的只讀的全局變數
     4         private static readonly Singleton singleton = new Singleton();
     5 
     6         /// <summary>
     7         /// 構造函數
     8         /// </summary>
     9         private Singleton()
    10         {
    11             //必須是私有的構造函數,這樣就可以保證該類無法通過new來創建該類的實例。
    12             //想要使用該類只能通過唯一訪問點GetInstance()。
    13         }
    14 
    15         /// <summary>
    16         /// 全局訪問點
    17         /// 設置為靜態方法則可在外邊無需創建該類的實例就可調用該方法
    18         /// </summary>
    19         /// <returns></returns>
    20         public static Singleton GetInstance()
    21         {
    22             return singleton;
    23         }
    24     }

    在c#中使用靜態初始化時無需顯示地編寫線程安全代碼,C# 與 CLR 會自動解決前面提到的懶漢式單例類時出現的多線程同步問題。

    當整個類被載入的時候,就會自行初始化 singleton 這個靜態只讀變數。

    而非在第一次調用 GetInstance()時再來實例化單例類的唯一實例,所以這就是一種餓漢式的單例類。

總結:

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

  (1)資源共用的情況下,避免由於資源操作時導致的性能或損耗等。如日誌文件,應用配置。

  (2)控制資源的情況下,方便資源之間的互相通信。如線程池等。

 

 

 

  

  

作者:少年真愛 出處:https://www.cnblogs.com/mingnianjiehunba/p/17669212.html 博主的文章沒有高度、深度和廣度,只是湊字數。由於博主的水平不高,不足和錯誤之處在所難免,希望大家能夠批評指出。 博主是利用讀書、參考、引用、抄襲、複製和粘貼等多種方式打造成自己的文章,請原諒博主成為一個無恥的文檔搬運工!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Redis 是一款記憶體資料庫,Redis Enterprise 在其基礎上增加了一整套涉及管理、自動化、彈性擴展以及安全等方面的功能,使其更適合企業級的應用場景。能夠為應用程式和資料庫提供更大的價值,更快地將應用程式推向市場,從數字渠道獲得更多收入,減少 DevOps 的工作量。 ...
  • 正常情況下,我們按照下麵的步驟操作即可進入Android的開發者模式(大部分安卓手機進入的方式都類似): 打開手機的設置,點擊最下麵的關於手機。 點擊這裡的“HarmonyOS版本”。連續點擊多次(我的手機是7次),然後會彈出需要輸入密碼解屏。解鎖之後屏幕上會提示“您正處於開發者模式!”。 返回到上 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 效果演示 橫版 豎版 思路分析 可以看到文字是一段一段的並且獨占一行,使用段落標簽p表示一行 一段文字內,字是一個一個顯示的,所以這裡每一個字都用一個span標簽裝起來 每一個字都是從透明到不透明的過渡效果,使用css3的過渡屬性tran ...
  • vue3.0父級組件調用子組件方法 場景:在頁面開發過程中,我經常涉及到不同組件之間的元素和方法的調用。就此記錄在vue3.0項目,也是我開發的開源項目中的實現方式。 父級組件調用子級 1.應用場景 以下以我的代碼實現為例:在左側菜單中,通過點擊新建會話,在會話列表中新建一個會話框。 其中:會話列表 ...
  • 前言 之前業務系統中驗證碼一直是由後端返回base64與一個驗證碼的字元串來實現的,想了下,前端其實可以直接canvas實現,減輕伺服器壓力。 實現 子組件,允許自定義圖片尺寸(預設尺寸為100 * 40)與驗證碼刷新時間(預設時間為60秒)。同時暴露繪製驗證碼方法drawPic(),允許父組件直接 ...
  • 公眾號「古時的風箏」,專註於後端技術,尤其是 Java 及周邊生態。 個人博客:www.moonkite.cn 大家好,我是風箏 最近這兩天,在前端圈最火的圖片莫過於下麵這張了。 這是一段 React 代碼,就算你完全沒用過 React 也沒關係,一眼看過去就能看到其中最敏感的一句代碼,就是那句 S ...
  • 1、什麼是for迴圈 在JavaScript中,for迴圈是一種常用的控制流語句,用於重覆執行一段代碼指定的次數。 for (迴圈變數初始化表達式; 迴圈條件表達式; 更新迴圈變數表達式) { 迴圈體} 初始化表達式:只在迴圈開始時執行一次,通常用於初始化迴圈控制變數。 條件表達式:每次迴圈開始前都 ...
  • 退款業務強耦合到售後系統中,並且業務代碼分散到各個業務層,嚴重缺乏系統的領域邊界和分層設計,重構後退款業務邏輯不強依賴售後核心業務邏輯,做到可以獨立部署。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...