1. 程式人生 > >C# 基礎(十四)C#單例模式:首先介紹 單執行緒、多執行緒、加鎖 單例模式。然後介紹單例模式的執行緒同步:多執行緒有序訪問共享記憶體。

C# 基礎(十四)C#單例模式:首先介紹 單執行緒、多執行緒、加鎖 單例模式。然後介紹單例模式的執行緒同步:多執行緒有序訪問共享記憶體。

一、簡介

本篇文章將介紹如何使用單例模式,也就是類的例項化,在整個專案的生命週期內,只例項化一次。在單例模式中,往往可以看到如SourceCode.cs:這樣的結構的。

SourceCode.cs:

public class Singleton {
    private static Singleton instance;
 
    private Singleton() {
    }
 
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

我的專案中,也有類似的結構:

好了,我把 private GlobalDataStruct()的程式碼顯示如下:

    private GlobalDataStruct()
        {
            m_cApplicationDir = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
            m_dbKrayConfig = new cKrayDBase();
            m_dbPatientInfo = new cKrayDBase();
            m_csStudyPatientInfo = new PatientInfo();
            m_csStudySeriesInfo = new SeriesInfo();
            m_csConfigFileInfo = new ConfigStructData();


            m_bEnableExpFlag = true;
            m_bConfigDbOpenFlag = false;
            m_bPatientDbOpenFlag = false;

            m_bHandPressStatus = false;
            m_bFootBreakStatus = false;
            m_bGenLoadStatus = false;
            m_bCanLoadStatus = false;
            m_bAlreadSelectCheckListStatus = false;
            m_bReviewCheckFlag = false;
            m_bHandCheckFlag = false;
            m_bSpliceFlag = false;
            m_bDRGenExpStatus = false;
            m_bFluGenExpStatus = false;
            m_bLoadCurCheckDeviceMode = true;

            m_sStudyBodyName = "";
            m_nFrameRate = 0;
            m_nGrabAcqStatus = 0;
            m_nSelectCheckIndex = 0;

            m_strDBDriver = "Provider = Microsoft.ACE.OLEDB.12.0; Data Source =";
            InitPublicInfo();
            InitDeviceInfo();
        }

並且,我把   public static GlobalDataStruct CreateInstance()的程式碼顯示如下:

 public static GlobalDataStruct CreateInstance()
        {
            //雙重加鎖
            if (_instance == null)
            {
                lock (lockobj)//執行緒同步問題
                {
                    if (_instance == null)
                    {
                        _instance = new GlobalDataStruct();
                    }
                }
            }

            return _instance;
        }

好了不用多說,你會發現SourceCode.cs和我截圖的程式碼結構,有著異曲同工之妙。並且發現,下面的幾部分基本相同

private static Singleton instance

private Singleton() {  }

public static  Singleton getInstance() {}

 

 好了,下面,我先來分析這三部分的內容,然後再分析單例模式的寫法。

二、單例模式的結構

主要參考(這三個連結的文章都很好):

https://www.cnblogs.com/zhili/p/SingletonPatterm.html

https://zhidao.baidu.com/question/547674677.html

https://blog.csdn.net/u012975370/article/details/62046317

單例模式的結構,往往如下:

public class Singleton {
    //類的例項化:必須是private,防止Singleton類例項化時對它進行更改。且用static保留一份。
    private static Singleton instance;
  
    //預設建構函式,但是外界不能建立該類例項
    private Singleton() {
    }
 
    //public給外部訪問。且用static保留一份。
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

private static Singleton instance;——表示類的例項化,用static來例項化。

private Singleton() { }—— 一般來說,C#類的預設建構函式,都是public的。但是這裡卻用到了private。原因很簡單,防止使用者使用建構函式重新構造,確保生成的例項是單一的。比如我再次例項,就不行啦,如下圖:

 public static Singleton getInstance() {} ——定義公有方法提供一個全域性訪問點。

         好了,單例模式的結構,我們已經基本弄明白了。接下來,看看單例模式的幾種常用寫法。

三、單例模式的幾種寫法

主要參考:

https://www.cnblogs.com/zhili/p/SingletonPatterm.html提到了單執行緒、多執行緒、加鎖

https://blog.csdn.net/u012975370/article/details/62046317提到了懶漢式、餓漢式、加鎖、列舉、靜態內部類、容器

1、單例模式(懶漢式,),在單執行緒下的實現方法:

/// <summary>
    /// 單例模式的實現
    /// </summary>
    public class Singleton
    {
        // 定義一個靜態變數來儲存類的例項
        private static Singleton uniqueInstance;

        // 定義私有建構函式,使外界不能建立該類例項
        private Singleton()
        {
        }

        /// <summary>
        /// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 如果類的例項不存在則建立,否則直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }

2、單例模式,在多執行緒下的實現方法:

方法1:簡單的加鎖

/// <summary>
    /// 單例模式的實現
    /// </summary>
    public class Singleton
    {
        // 定義一個靜態變數來儲存類的例項
        private static Singleton uniqueInstance;

        // 定義一個標識確保執行緒同步
        private static readonly object locker = new object();

        // 定義私有建構函式,使外界不能建立該類例項
        private Singleton()
        {
        }

        /// <summary>
        /// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 當第一個執行緒執行到這裡時,此時會對locker物件 "加鎖",
            // 當第二個執行緒執行該方法時,首先檢測到locker物件為"加鎖"狀態,該執行緒就會掛起等待第一個執行緒解鎖
            // lock語句執行完之後(即執行緒執行完之後)會對該物件"解鎖"
            lock (locker)
            {
                // 如果類的例項不存在則建立,否則直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new Singleton();
                }
            }

            return uniqueInstance;
        }
    }

方法2:雙重加鎖方式

與方法不同的是,雙重加鎖效率更高,只需要新增 if (uniqueInstance == null) {}   這行即可。

/// <summary>
    /// 單例模式的實現
    /// </summary>
    public class Singleton
    {
        // 定義一個靜態變數來儲存類的例項
        private volatile static Singleton uniqueInstance;

        // 定義一個標識確保執行緒同步
        private static readonly object locker = new object();

        // 定義私有建構函式,使外界不能建立該類例項
        private Singleton()
        {
        }

        /// <summary>
        /// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 當第一個執行緒執行到這裡時,此時會對locker物件 "加鎖",
            // 當第二個執行緒執行該方法時,首先檢測到locker物件為"加鎖"狀態,該執行緒就會掛起等待第一個執行緒解鎖
            // lock語句執行完之後(即執行緒執行完之後)會對該物件"解鎖"
            // 雙重鎖定只需要一句判斷就可以了
            if (uniqueInstance == null)
            {
                lock (locker)
                {
                    // 如果類的例項不存在則建立,否則直接返回
                    if (uniqueInstance == null)
                    {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }

 好了,你可能會注意到,雙重加鎖程式碼中,多了volatile 關鍵字,接下來分析volatile關鍵字。volatile關鍵字與共享記憶體有關。

四、共享記憶體(volatile關鍵字)、執行緒同步

執行緒同步:就是要保證多個執行緒  安全地 訪問共享記憶體。

主要參考:

https://blog.csdn.net/u012975370/article/details/62046317

https://blog.csdn.net/lonestar555/article/details/49025169

多個執行緒同時訪問一個變數,CLR為了效率,允許每個執行緒進行本地快取,這就導致了變數的不一致性。volatile就是為了解決這個問題,volatile修飾的變數,不允許執行緒進行本地快取,每個執行緒的讀寫都是直接操作在共享記憶體上,這就保證了變數始終具有一致性。即共享記憶體允許多個執行緒有序地讀寫變數,這些變數儲存在共享記憶體上,保證了變數的一致性(比如單例項類)。

public class Singleton {
    private volatile static Singleton instance = null;
 
    public static Singleton getInstance() {
        Singleton inst = instance; // 建立臨時變數
        if (inst == null) {
            synchronized (Singleton.class) {
                inst = instance;
                if (inst == null) {
                    inst = new Singleton();
                }
            }
        }
        return inst; // 返回臨時變數
    }
 
    private Singleton() {}
}

五、總結

執行緒同步問題,雖然可以保證安全地訪問共享記憶體,但是效率較慢。