OpenKey.Cloud 作為 ChatGPT 生態圈內的重要基礎設施,提供官方 API 的轉發,長久以來一直保持著高穩定性,這是如何做到的?今天就來揭秘 OpenKey 系統的詳細架構圖。 ...
基礎介紹:
確保一個類只有一個實例,並提供一個全局訪問點。
適用於需要頻繁實例化然後銷毀的對象,創建對象消耗資源過多,但又經常用到的對象,頻繁訪問資料庫或文件的對象。
其本質就是保證在整個應用程式的生命周期中,任何一個時刻,單例類的實例都只存在一個。
- 特性和功能:確保一個類只有一個實例,並提供一個全局訪問點。
- 使用環境:當類只需要一個實例,且易於訪問,且實例應在整個應用程式中共用時。
- 註意事項:需要註意線程安全問題。
- 優點:可以確保一個類只有一個實例,減少了記憶體開銷。
- 缺點:沒有介面,擴展困難。
應用場景:
單例模式通常適用於在整個應用程式中只需要一個實例化對象的場景,以確保資源的高效利用和應用程式的穩定性。(共用資源)
資源共用的情況下,避免由於資源操作時導致的性能或損耗等。
控制資源的情況下,方便資源之間的互相通信。如線程池等。
- 日誌系統:在應用程式中,通常只需要一個日誌系統,以避免在多個地方創建多個日誌對象。這一般是由於共用的日誌文件一直處於打開狀態,所以只能有一個實例去操作,否則內容不好追加也有可能造成資源占用加劇資源消耗。
- 資料庫連接池:在應用程式中,資料庫連接池是一個非常重要的資源,單例模式可以確保在應用程式中只有一個資料庫連接池實例,避免資源浪費。主要是節省打開或者關閉資料庫連接所引起的效率損耗,因為何用單例模式來維護,就可以大大降低這種損耗。
- 配置文件管理器:在應用程式中,通常只需要一個配置文件管理器來管理應用程式的配置文件,單例模式可以確保在整個應用程式中只有一個配置文件管理器實例。這個是由於配置文件是共用的資源。
- 緩存系統:在應用程式中,緩存系統是一個重要的組件,單例模式可以確保在整個應用程式中只有一個緩存實例,以提高應用程式的性能。
- 網站線上人數統計:其實就是全局計數器,也就是說所有用戶在相同的時刻獲取到的線上人數數量都是一致的。
- GUI組件:在圖形用戶界面(GUI)開發中,單例模式可以確保在整個應用程式中只有一個GUI組件實例,以確保用戶界面的一致性和穩定性。
創建方式:
餓漢式:類載入就會導致該單實例對象被創建。(靜態變數方式、靜態代碼塊方式)
懶漢式:類載入不會導致該單實例對象被創建,而是首次使用該對象時才會創建。(線程不安全型、線程安全型、雙重檢查鎖)
-
懶漢式---非線程安全型
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線程安全型。
-
懶漢式---線程安全型
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。
-
懶漢式---雙重檢查鎖
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進行使用。
-
餓漢式
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 博主的文章沒有高度、深度和廣度,只是湊字數。由於博主的水平不高,不足和錯誤之處在所難免,希望大家能夠批評指出。 博主是利用讀書、參考、引用、抄襲、複製和粘貼等多種方式打造成自己的文章,請原諒博主成為一個無恥的文檔搬運工!