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

通常我們在寫程序的時候會碰到一個類只允許在整個系統中只存在一個實例(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;}
}

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/253900.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/253900.shtml
英文地址,請注明出處:http://en.pswp.cn/news/253900.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

修改輸入框placeholder的默認樣式

一般網頁中都用到input的placeholder屬性&#xff0c;想讓這個默認樣式和網頁保持一致&#xff0c;就需要重新設定樣式&#xff0c;百度百度&#xff1a; :-moz-placeholder { / color: #000; opacity:1; }支持/* Mozilla Firefox 4 to 18 * ::-moz-placeholder { color: #000;…

進程及線程通信總結

上文我們介紹了如何建立一個簡單的多線程程序&#xff0c;多線程之間不可避免的需要進行通信 。相比于進程間通信來說&#xff0c;線程間通信無疑是相對比較簡單的。 首先我們來看看最簡單的方法&#xff0c;那就是使用全局變量&#xff08;靜態變量也可以&#xff09;來進行通…

ROS multi-master——multimaster_fkie配置

多主站ROS配置和mutimaster_fkie ROS版本&#xff1a;kinetic 操作系統&#xff1a;Ubuntu 16.04。 multimaster_fkie&#xff1a;github 1網絡配置 1.1路由器 設置無線路由器并連接兩臺計算機/機器人。為這兩臺計算機設置靜態IP地址。相互測試ping命令和ssh。 1.2主機 …

Docker入門

1. Docker簡介: docker是一個基于LXC的高級容器引擎。簡單地說&#xff0c;docker是一個輕量級的虛擬解決方案&#xff0c;或者說它是一個超輕量級的虛擬機&#xff08;容器&#xff09;。 Docker是一個開源的引擎&#xff0c;可以輕松的為任何應用創建一個輕量級的、可移植的、…

樂器庫的混合

每臺微機一個聲卡&#xff0c;也就是一片D/A&#xff0c;驅動按波特率、采樣位數、采樣通道傳輸給D/A&#xff0c;輸出模擬音頻。播放時僅一種與硬件支持格式對應&#xff0c;其他需驅動&#xff08;有損&#xff09;變換到硬件支持格式。每個應用都可單獨播放聲音&#xff0c;…

kafka認知--(1)

文檔參考&#xff1a;http://kafka.apache.org/documentation.html 下載代碼&#xff1a; tar -xzf kafka_2.11-0.11.0.0.tgz cd kafka_2.11-0.11.0.0 啟動 bin/zookeeper-server-start.sh config/zookeeper.properties 創建topic&#xff08;主題為test&#xff0c;只有一個分…

帶頭尾指針的list的C實現

一、緣起 很早寫了一個帶頭尾指針的list&#xff0c;該list支持從尾部插入元素&#xff0c;在任意位置刪除元素&#xff0c;最近用這個list時發現一個bug&#xff0c;修正了&#xff0c;并加了幾個接口函數。貼出來&#xff0c;希望對C的初學者有用。 二、基本說明 2.1、數據結…

Gmapping——從原理到實踐

概述 在SLAM中&#xff0c;機器人位姿和地圖都是狀態變量&#xff0c;我們需要同時對這兩個狀態變量進行估計&#xff0c;即機器人獲得一張環境地圖的同時確定自己相對于該地圖的位置。我們用x表示機器人狀態&#xff0c;m表示環境地圖&#xff0c;z表示傳感器觀測情況&#xf…

關于git分支

1.關于git分支 git的“分支”乍一聽是一個特別容易讓人產生錯覺的概念&#xff0c;以為它和樹枝一樣是分叉的枝節&#xff0c;其實Git中的分支本質上是個指向commit對象的指針,每次commit&#xff0c;git都把它們串成一條時間線&#xff0c;這條時間線就是一個分支。 2.直接切換…

【機器學習經典算法源碼分析系列】-- 邏輯回歸

1.邏輯回歸&#xff08;Logistic Regression&#xff09;又常被成為“邏輯斯蒂回歸”&#xff0c;實質上是一個二元分類問題。 邏輯回歸代價函數&#xff1a; 代價函數導數&#xff1a; Matlab實現&#xff1a; 采用matlab中自帶的無約束最小化函數fminunc來代替梯度下降法&…

求特殊自然數

總時間限制: 1000ms 內存限制: 65536kB 描述一個十進制自然數,它的七進制與九進制表示都是三位數&#xff0c;且七進制與九進制的三位數碼表示順序正好相反。編程求此自然數,并輸出顯示。 輸入無。輸出三行&#xff1a;第一行是此自然數的十進制表示&#xff1b;第一行是此自然…

ROS——不同版本間ROS進行通信

在相同版本間的ROS進行通信不在贅述了&#xff0c;修改/etc/hosts文件即可。 最近項目遇到在Ubuntu16.04 與Ubuntu18.04兩個系統間進行ROS通信&#xff0c;ROS版本分別為Kinetic和Melodic。配置網絡后&#xff0c;兩邊都能夠ping通&#xff0c;但是在獲取ros數據是&#xff0c…

大數據開發實戰:數據流圖及相關數據技術

1、大數據流程圖 2、大數據各個環節主要技術 2.1、數據處理主要技術 Sqoop&#xff1a;&#xff08;發音&#xff1a;skup&#xff09;作為一款開源的離線數據傳輸工具&#xff0c;主要用于Hadoop(Hive) 與傳統數據庫&#xff08;MySql,PostgreSQL&#xff09;間的數據傳遞。它…

oracle 中時間類型 date 與 long 互轉

我們在保存時間到數據庫時&#xff0c;有時候會保存long型的數據&#xff0c;固定長度是13位&#xff0c;是用當前時間減去1970-01-01&#xff0c;再換算成毫秒得到的結果。 但是要考慮到時區問題中國的時區時8區所以時間要加上8小時 oracle中的實現方式&#xff1a; ---------…

Linux對包管理闡述

Centos/Redhat/Fedora的軟件包&#xff0c;都是rpm后綴的文件。包管理器rpm(Redhat packages manager) linux的哲學思想是簡單命令解決復雜任務&#xff0c;因此每個軟件的功能較單一&#xff0c;所以各種包之間有著復雜的依賴關系&#xff0c;為了解決這種可以使用前端工具&am…

跨時鐘域電路設計——亞穩態及雙鎖存器

一、同步電路 定義&#xff1a;電路中所有受時鐘控制的單元&#xff0c;全部由一個統一的時鐘控制。 優點&#xff1a;在同步設計中&#xff0c;EDA工具可以保證電路系統的時序收斂&#xff0c;避免電路設計中的競爭冒險。 缺點&#xff1a;時鐘樹綜合需要加入大量延遲單元&…

linux setsockopt詳解

1.closesocket&#xff08;一般不會立即關閉而經歷TIME_WAIT的過程&#xff09;后想繼續重用該socket&#xff1a; BOOL bReuseaddrTRUE; setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL)); 2. 如果要已經處于連接狀態的soket在調用closes…

[TOOLS] 移動端調試進行時 - whistle

1、本地安裝、啟動whistle 安裝實操請查看官方文檔不贅述 復制代碼 2、手機設置代理 實操請查看官方文檔 !!!注意&#xff1a;代理ip填寫whistle右上角online選項中的ip 復制代碼 3、whistle上設置對應rules、weinre whistle設置代理(!!!注意支持tunnel協議)&#xff1a; rules…

函數動態參數實現format

變量賦值一種是字符串格式化&#xff0c;一種是通過format的方式 1.字符串格式化 s"i am %s,age %d"%(Jasper,23)print(s)打印輸出&#xff1a;i am Jasper,age 232.format格式化 s"i am {name},age {age}".format(namejasper,age23)print(s)或 s2"i …

跨時鐘域電路設計——單bit信號

前面提到了簡單的雙電平鎖存器&#xff0c;下面是一些單bit同步電路。 一、慢時鐘域向快時鐘域 邊沿檢測同步器 將慢時鐘域的脈沖搬移并縮小為快時鐘域的脈沖。 既可以檢測上升沿&#xff0c;也可以檢測下降沿。 如上圖&#xff0c;慢時鐘下一個有效脈沖的最短周期為慢時鐘的…