主頁 > .NET開發 > 單例模式使用餓漢式和懶漢式創建一定安全?很多人不知

單例模式使用餓漢式和懶漢式創建一定安全?很多人不知

2022-08-04 10:22:14 .NET開發

概述

單例模式大概是23種設計模式里面用的最多,也用的最普遍的了,也是很多很多人一問設計模式都有哪些必答的第一種了;我們先復習一下餓漢式和懶漢式的單例模式,再談其創建方式會帶來什么問題,并一一解決!還是老規矩,先上代碼,不上代碼,紙上談兵咱把握不住,

餓漢式代碼

    public class SingleHungry
    {
        private readonly static SingleHungry _singleHungry = new SingleHungry();
        private SingleHungry()
        {
        }
        public static SingleHungry GetSingleHungry()
        {
            return _singleHungry;
        }
    }

代碼很簡單,意思也很明確,接著我們寫點代碼測驗驗證一下;

第一種測驗: 建構式私有的,new的時候報錯,因為我們的建構式是私有的,

 SingleHungry  _singleHungry=new SingleHungry();
第二種測驗: 比對創建多個物件,然后多個物件的Hashvalue
public class SingleHungryTest
    {
        public static void FactTestHashCodeIsSame()
        {
            Console.WriteLine("單例模式.餓漢式測驗!");
            var single1 = SingleHungry.GetSingleHungry();
            var single2 = SingleHungry.GetSingleHungry();
            var single3 = SingleHungry.GetSingleHungry();
            Console.WriteLine(single1.GetHashCode());
            Console.WriteLine(single2.GetHashCode());
            Console.WriteLine(single3.GetHashCode());
        }
    }
測驗下來,三個物件的hash值是一樣的,如下圖:

餓漢式結論總結

餓漢式的單例模式不推薦使用,因為還沒呼叫,物件就已經創建,造成資源的浪費;

懶漢式代碼

    public class SingleLayMan
    {
        //1、私有化建構式
        private SingleLayMan()
        {

        }
        //2、宣告靜態欄位  存盤我們唯一的物件實體
        private static SingleLayMan _singleLayMan;
        //通過方法 創建實體并回傳
        public static SingleLayMan GetSingleLayMan1()
        {
            //這種方式不可用  會創建多個物件,謹記
            return _singleLayMan = new SingleLayMan();
        }
        /// <summary>
        ///懶漢式單例模式只有在呼叫方法時才會去創建,不會造成資源的浪費
        /// </summary>
        /// <returns></returns>
        public static SingleLayMan GetSingleLayMan2()
        {
            if (_singleLayMan == null)
            {
                Console.WriteLine("我被創建了一次!");
                _singleLayMan = new SingleLayMan();
            }
            return _singleLayMan;
        }
    }

測驗代碼

 public class SingleLayManTest
    {
        /// <summary>
        /// 會創建多個物件.hash值不一樣
        /// </summary>
        public static void FactTest()
        {
            Console.WriteLine("單例模式.懶漢式測驗!");
            var singleLayMan1 = SingleLayMan.GetSingleLayMan1();
            var singleLayMan2 = SingleLayMan.GetSingleLayMan1();
            Console.WriteLine(singleLayMan1.GetHashCode());
            Console.WriteLine(singleLayMan2.GetHashCode());
        }
        /// <summary>
        /// 單例模式.懶漢式測驗:懶漢式單例模式只有在呼叫方法時才會去創建,不會造成資源的浪費,但會有執行緒安全問題
        /// </summary>
        public static void FactTest1()
        {
            Console.WriteLine("單例模式.懶漢式測驗!");
            var singleLayMan1 = SingleLayMan.GetSingleLayMan2();
            var singleLayMan2 = SingleLayMan.GetSingleLayMan2();
            Console.WriteLine(singleLayMan1.GetHashCode());
            Console.WriteLine(singleLayMan2.GetHashCode());
        }
        /// <summary>
        /// 單例模式.懶漢式多執行緒環境測驗!
        /// </summary>
        public static void FactTest2()
        {
            Console.WriteLine("單例模式.懶漢式多執行緒環境測驗!");
            for (int i = 0; i < 10; i++)
            {
                new Thread(() =>
                {
                    SingleLayMan.GetSingleLayMan2();
                }).Start();
            }

            //Parallel.For(0, 10, d => {
            //    SingleLayMan.GetSingleLayMan2();
            //});
        }
    }

懶漢式結論總結

懶漢式的代碼如上已經概述,上面GetSingleLayMan1()會創建多個物件,這個沒什么好說的,肯定不推薦使用;GetSingleLayMan2()是大多數人經常使用的,可解決剛才因為餓漢式創建帶來的缺點,但也帶來了多執行緒的問題,如果不考慮多執行緒,那是夠用了,



話說回來,既然剛才餓漢式和懶漢式各有其優缺點,那我們該如何抉擇呢?到底選擇哪一種?

其它方式創建單例—餓漢式+靜態內部類

    public class SingleHungry2
    {
        public static SingleHungry2 GetSingleHungry()
        {
            return InnerClass._singleHungry;
        }       
        public static class InnerClass
        {
            public readonly static SingleHungry2 _singleHungry = new SingleHungry2();
        }
    }

這個代碼,用了餓漢式結合靜態內部類來創建單例,執行緒也安全,不失為創建單例的一種辦法,

其它方式創建單例—懶漢式+反射

 首先我們解決一下剛才懶漢式創建單例的執行緒安全問題,上代碼:

 /// <summary>
    /// 通過反射破壞創建物件
    /// </summary>
    public class SingleLayMan1
    { 
        //私有化建構式
        private SingleLayMan1()
        {
        }
        //2、宣告靜態欄位  存盤我們唯一的物件實體
        private static SingleLayMan1? _singleLayMan;
        private static object _oj = new object();

/// <summary> /// //解決多執行緒安全問題,雙重鎖定,減少系統消耗,節約資源 /// </summary> public static SingleLayMan1 GetSingleLayMan() { if (_singleLayMan == null) { lock (_oj) { if (_singleLayMan == null) { _singleLayMan = new SingleLayMan1(); Console.WriteLine("我被創建了一次!"); } } } return _singleLayMan; } }

具體描述,在代碼里面已經說得足夠清楚,一看肯定明白,我們還是寫點測驗代碼,驗證一下,上代碼:

public class SingleLayManTest1
    {
        public static void FactTestReflection()
        {
            var singleLayMan1= SingleLayMan1.GetSingleLayMan();

            var type = Type.GetType("_01單例模式.反射破壞單例模式.SingleLayMan1");
            //獲取私有的建構式
            var ctors = type?.GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            //執行建構式
            SingleLayMan1 singleLayMan = (SingleLayMan1)ctors[0].Invoke(null);
            Console.WriteLine(singleLayMan1.GetHashCode());
            Console.WriteLine(singleLayMan.GetHashCode());
        }
    }

上面的代碼分別通過SingleLayMan1.GetSingleLayMan2()和反射創建物件,輸出二者物件hash值比較,結果肯定是不一樣的,重點是我們可以通過反射創建物件,

通過上面的代碼,不知道大家有沒有意識到我們雖通過加鎖解決了執行緒安全問題,但仍會出現問題;正常創建物件的順序是:

1、new 在記憶體中開辟空間
2、 執行建構式 創建物件
3、 把空間指向我們的對像

但如果因為我們的程式使用多執行緒,則會發生"指令重排",本應執行順序為1、2、3,實際執行順序為1、3、2,但這種情況很少,不過我們寫程式嘛,肯定追求嚴謹一點準沒錯,

如果需要解決該問題需要給定義的私有區域變數加關鍵字 加上volatile (意思不穩定的 ,可變的) ,加該關鍵字可以避免指令重排,具體代碼主要是這句如下:

 private volatile static SingleLayMan? _singleLayMan;

 

到這里,大家認為還有沒有問題?答案是肯定的,不然我就不會寫這篇文章了,通過反射既然可以創建物件,那么我們寫的創建實體代碼還有什么意義,有沒有什么辦法避免反射創建物件呢?

如果認真看了之前的反射創建物件代碼,肯定發現反射是通過建構式來創建物件的,那么我們相應的就在建構式處理一下,來,我們繼續上代碼:

 /// <summary>
    /// 解決反射創建物件的問題
    /// </summary>
    public class SingleLayMan3
    {
        //2、宣告靜態欄位  存盤我們唯一的物件實體
        private volatile static SingleLayMan3? _singleLayMan;
        private static object _oj = new object();
        //私有化建構式
        private SingleLayMan3()
        {
            lock (_oj)
            {
                if (_singleLayMan != null)
                {
                    throw new Exception("不要通過反射來創建對像!");
                }
            }
        }

        /// <summary>
        /// //解決多執行緒安全問題,雙重鎖定,減少系統消耗,節約資源
        /// </summary>
        public static SingleLayMan3 GetSingleLayMan()
        {
            if (_singleLayMan == null)
            {
                lock (_oj)
                {
                    if (_singleLayMan == null)
                    {
                        _singleLayMan = new SingleLayMan3();
                        Console.WriteLine("我被創建了一次!");
                    }
                }
            }           
            return _singleLayMan;
        }
       
    }

下面繼續上測驗代碼,驗證一下:

public class SingleLayManTest3
    {
        /// <summary>
        /// 第一次通過呼叫 SingleLayMan3.GetSingleLayMan()創建物件導致_singleLayMan不為空,之后再去通過反射創建物件時,建構式里面判斷創建物件導致_singleLayMan變數,報例外
        /// </summary>
        public static void FactTestReflection()
        {
            var singleLayMan1= SingleLayMan3.GetSingleLayMan();

            var type = Type.GetType("_01單例模式.反射破壞單例模式.SingleLayMan3");
            //獲取私有的建構式
            var ctors = type?.GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            //執行建構式
            SingleLayMan3 singleLayMan = (SingleLayMan3)ctors[0].Invoke(null);
            Console.WriteLine(singleLayMan1.GetHashCode());
            Console.WriteLine(singleLayMan.GetHashCode());
        }
    }

結論其實測驗方法已經說明:第一次通過呼叫 SingleLayMan3.GetSingleLayMan()創建物件導致_singleLayMan不為空,之后再去通過反射創建物件時,建構式里面判斷創建物件導致_singleLayMan變數,報例外,

其實到這里,有人肯定發現了問題,第一次通過去執行自己寫的創建單例方法來創建物件,后面再執行反射時才會報例外,那有沒有什么辦法,只要有人第一次反射創建物件時就報例外呢?

定義區域變數解決反射創建物件問題

 public class SingleLayMan4
    {
        //2、宣告靜態欄位  存盤我們唯一的物件實體
        private volatile static SingleLayMan4? _singleLayMan;
        private static object _oj = new object();
        private static bool _isOk = false;
        //私有化建構式
        private SingleLayMan4()
        {
            lock (_oj)
            {
                if (_isOk == false)
                {
                    _isOk = true;
                }
                else
                {
                    throw new Exception("不要通過反射來創建對像!只有第一次通過反射創建物件會成功!請做第一個吃葡萄的人!");
                }
            }
        }

        /// <summary>
        /// //解決多執行緒安全問題,雙重鎖定,減少系統消耗,節約資源
        /// </summary>
        public static SingleLayMan4 GetSingleLayMan()
        {
            if (_singleLayMan == null)
            {
                lock (_oj)
                {
                    if (_singleLayMan == null)
                    {
                        _singleLayMan = new SingleLayMan4();
                        Console.WriteLine("我被創建了一次!");
                    }
                }
            }           
            return _singleLayMan;
        }
       
    }

測驗代碼,驗證一下:

public static void FactTestReflection()
        {
            //第一次創建物件會成功
            var singleLayMan1 = GetReflectionSingleLayMan4Instance();

            //第二次創建物件會失敗,報例外
           var singleLayMan2 = GetReflectionSingleLayMan4Instance();

            Console.WriteLine(singleLayMan1.GetHashCode());
        }
        private static SingleLayMan4 GetReflectionSingleLayMan4Instance()
        {
            var type = Type.GetType("_01單例模式.反射破壞單例模式.SingleLayMan4");
            //獲取私有的建構式
            var ctors = type?.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
            //執行建構式
            SingleLayMan4 singleLayMan = (SingleLayMan4)ctors[0].Invoke(null);
            return singleLayMan;
        }

第一次創建物件會成功,因為執行建構式時沒有執行GetSingleLayMan(),跨過了new,導致_isOk賦值true,第二次反射創建執行建構式時判斷變數_isOk為true,走入例外邏輯,

但這樣做真的就安全了嗎?既然可以通過反射執行建構式來創建物件,那也可以通過反射改變區域變數_isOk 的值,上代碼:

        /// <summary>
        /// 通過反射也可以改變區域變數_isOk的值,繼續創建物件
        /// </summary>
        public static void FactTestReflection2()
        {
            Type type = Type.GetType("_01單例模式.反射破壞單例模式.SingleLayMan4");
            //獲取私有的建構式
            var ctors = type?.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
            //執行建構式
            SingleLayMan4 singleLayMan1 = (SingleLayMan4)ctors[0].Invoke(null);
            FieldInfo fieldInfo =  type.GetField("_isOk", BindingFlags.NonPublic | BindingFlags.Static);
            fieldInfo.SetValue("_isOk", false);
            SingleLayMan4 singleLayMan2 = (SingleLayMan4)ctors[0].Invoke(null);

            Console.WriteLine(singleLayMan1.GetHashCode());
            Console.WriteLine(singleLayMan2.GetHashCode());
        }

最后

大家或許發現了,只要有反射存在,哪怕你的邏輯寫的再嚴謹,它仍然可以反射創建物件,只因為它是反射!所以,單例模式的安全性也是相對而言的,具體選擇用哪個,取決專案的業務場景了,如有發現問題,歡迎不吝賜教!

原始碼地址:https://gitee.com/mhg/design-mode-demo.git




    

作者:課間一起牛

出處:https://www.cnblogs.com/mhg215/

聲援博主:如果您覺得文章對您有幫助,請點擊文章末尾的【關注我】吧!

別忘記點擊文章右下角的【推薦】支持一波,~~~///(^v^)\\\~~~ .

本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,

如果您有其他問題,也歡迎關注我下方的公眾號,可以聯系我一起交流切磋!

碼云:碼云      github:github

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/500901.html

標籤:.NET技术

上一篇:單例模式使用餓漢式和懶漢式創建一定安全?很多人不知

下一篇:SkiaSharp 之 WPF 自繪 粒子花園(案例版)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more