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);
}
}
}

