c# 變量,對象,靜態類型,集合類的線程安全回顧


   1.變量的線程安全性與變量的作用域有關。

   2.對象 

     對象是類型的實例

     在創建對象時,會單獨有內存區域存儲對象的屬性和方法。所以,一個類型的多個實例,在執行時,只要沒有靜態變量的參與,應該都是線程安全的。

這跟我們調試狀態下,是不一樣的。調試狀態下,如果多個線程都創建某實例的對象,每個對象都調用自身方法,在調試是,會發現是訪問的同一個代碼,多個線程是有沖突的。但是,真正的運行環境是線程安全的。

      以銷售員為例,假設產品是充足的,多個銷售員,銷售產品,調用方法:Sale(),其是線程安全的。

      但是,如果涉及到倉庫,必須倉庫有足夠的產品才能進行銷售,這時,多個銷售人員就有了臨界資源:倉庫。

      在這里我們只討論對象的普通方法。至於方法傳入的參數,以及方法內對靜態變量操作的,這里需要根據參數和靜態變量來判定方法的線程安全性。

      銷售員案例:

復制代碼
using System;
using System.Threading;

namespace MutiThreadSample.Sale
{
    /// <summary>
    /// 銷售
    /// </summary>
    public class Saler
    {
        /// <summary>
        /// 名稱
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 間隔時間
        /// </summary>
        public int IntervalTime { get; set; }
        /// <summary>
        /// 單位時間銷售運量
        /// </summary>
        public int SaleAmount { get; set; }
        /// <summary>
        /// 銷售
        /// </summary>
        public void Sale()
        {
            Console.WriteLine("銷售:{0} 於 {1} 銷售產品 {2}", this.Name, DateTime.Now.Millisecond, this.SaleAmount);
            Thread.Sleep(IntervalTime);
        }
        /// <summary>
        /// 銷售
        /// </summary>
        /// <param name="interval">時間間隔</param>
        public void Sale(object obj)
        {
            WHouseThreadParameter parameter = obj as WHouseThreadParameter;
            if (parameter != null)
            {
                while (parameter.WHouse != null && parameter.WHouse.CanOut(this.SaleAmount))
                {
                    parameter.WHouse.Outgoing(this.SaleAmount);
                    Console.WriteLine("Thread{0}, 銷售:{1} 於 {2} 銷售出庫產品 {3}", Thread.CurrentThread.Name, this.Name, DateTime.Now.Millisecond, this.SaleAmount);
                    Thread.Sleep(this.IntervalTime);
                }
            }
        }
     }
}
復制代碼

      3靜態類型

      已經講了類的實例--對象的多線程安全性問題。這里只討論類型的靜態變量和靜態方法。

      當靜態類被訪問的時候,CLR會調用類的靜態構造器(類型構造器),創建靜態類的類型對象,CLR希望確保每個應用程序域內只執行一次類型構造器,為了做到這一點,在調用類型構造器時,CLR會為靜態類加一個互斥的線程同步鎖,因此,如果多個線程試圖同時調用某個類型的靜態構造器時,那么只有一個線程可以獲得對靜態類的訪問權,其他的線程都被阻塞。第一個線程執行完 類型構造器的代碼並釋放構造器之后,其他阻塞的線程被喚醒,然后發現構造器被執行過,因此,這些線程不再執行構造器,只是從構造器簡單的返回。如果再一次調用這些方法,CLR就會意識到類型構造器被執行過,從而不會在被調用。

      調用類中的靜態方法,或者訪問類中的靜態成員變量,過程同上,所以說靜態類是線程安全的。

      最簡單的例子,就是數據庫操作幫助類。這個類的方法和屬性是線程安全的。

 

復制代碼
using System;

namespace MutiThreadSample.Static
{
    public class SqlHelper
    {        
        /// <summary>
        /// 數據庫連接
        /// </summary>
        private static readonly string ConnectionString = "";
        /// <summary>
        /// 執行數據庫命令
        /// </summary>
        /// <param name="sql">SQL語句</param>
        public static void ExcuteNonQuery(string sql)
        { 
            //執行數據操作,比如新增、編輯、刪除
        }
    }
}
復制代碼

 

       但是,對於靜態變量其線程安全性是相對的,如果多個線程來修改靜態變量,這就不一定是線程安全的。而靜態方法的線程安全性,直接跟傳入的參數有關。

       總之:

      針對變量、對象、類型,說線程安全性,比較籠統,在這里,主要是想讓大家明白,哪些地方需要注意線程安全性。對於變量、對象(屬性、方法)、靜態變量、靜態方法,其線程安全性是相對的,需要根據實際情況而定。

     萬劍不離其宗,其判定標准:是否有臨界資源。

 4、集合類型是線程安全的嗎?

      常用的集合類型有List、Dictionary、HashTable、HashMap等。在編碼中,集合應用很廣泛中,常用集合來自定義Cache,這時候必須考慮線程同步問題。 

      默認情況下集合不是線程安全的。在System.Collections 命名空間中只有幾個類提供Synchronize方法,該方法能夠超越集合創建線程安全包裝。但是,System.Collections命名空間中的所有類都提供SyncRoot屬性,可供派生類創建自己的線程安全包裝。還提供了IsSynchronized屬性以確定集合是否是線程安全的。但是ICollection泛型接口中不提供同步功能,非泛型接口支持這個功能。

     Dictionary(MSDN解釋)

     此類型的公共靜態(在 Visual Basic 中為 Shared)成員是線程安全的。 但不保證所有實例成員都是線程安全的。
     只要不修改該集合,Dictionary<TKey, TValue> 就可以同時支持多個閱讀器。 即便如此,從頭到尾對一個集合進行枚舉本質上並不是一個線程安全的過程。 當出現枚舉與寫訪問互相爭用這種極少發生的情況時,必須在整個枚舉過程中鎖定集合。 若允許多個線程對集合執行讀寫操作,您必須實現自己的同步。

      很多集合類型都和Dictionary類似。默認情況下是線程不安全的。當然微軟也提供了線程安全的Hashtable.

      HashTable 

      Hashtable 是線程安全的,可由多個讀取器線程和一個寫入線程使用。 多線程使用時,如果只有一個線程執行寫入(更新)操作,則它是線程安全的,從而允許進行無鎖定的讀取(若編寫器序列化為 Hashtable)。 若要支持多個編寫器,如果沒有任何線程在讀取 Hashtable 對象,則對 Hashtable 的所有操作都必須通過 Synchronized 方法返回的包裝完成。

      從頭到尾對一個集合進行枚舉本質上並不是一個線程安全的過程。 即使某個集合已同步,其他線程仍可以修改該集合,這會導致枚舉數引發異常。 若要在枚舉過程中保證線程安全,可以在整個枚舉過程中鎖定集合,或者捕捉由於其他線程進行的更改而引發的異常。

     線程安全起見請使用以下方法聲明

        /// <summary>
        ///  Syncronized方法用來創造一個新的對象的線程安全包裝
        /// </summary>
        private Hashtable hashtable = Hashtable.Synchronized(new Hashtable());

    在枚舉讀取時,加lock,這里lock其同步對象SyncRoot 

復制代碼
        /// <summary>
        /// 讀取
        /// </summary>
        public void Read()
        { 
            lock(hashtable.SyncRoot)
            {
                foreach (var item in hashtable.Keys)
                {
                    Console.WriteLine("Key:{0}",item);
                }
            }
        }
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM