執行緒安全(ThreadSafety)
這節講一下執行緒安全的例子,以及如何解決執行緒安全問題。
上節提到了執行緒安全的問題,說了一個例子,1000個人搶100張票,這節就從此案例著手,下面先看一下程式碼實現:
private static int tickets = 100; static void Main(string[] args) { Thread thread = BuyTicket(); Thread thread2 = BuyTicket(); Thread thread3 = BuyTicket(); thread.Name = "Thread1"; thread2.Name = "Thread2"; thread3.Name = "Thread3"; thread.Start(); thread2.Start(); thread3.Start(); Thread.Sleep(5000); } private static Thread BuyTicket() { Thread thread = new Thread(() => { while (tickets > 0) { Console.WriteLine($"{Thread.CurrentThread.Name}---------------->買了一張票,票號為:{tickets}"); tickets--; } }); thread.IsBackground = true; return thread; }
現有三個執行緒,同時訪問共享資源tickets,我們先來看一下執行結果:
100賣出了三次,這就是很明顯的執行緒安全問題,也就是說,他們都同時進入到了while塊中,同時拿到了tickets為100的值,所以我們解決執行緒安全問題,就要從此處下手,讓執行緒訪問共享資料的時候,同一時刻只能有一個執行緒去訪問。
lock鎖
解決執行緒安全的方法就是加鎖(同步鎖,互斥鎖),現在將程式碼改一下,使其執行緒安全:
private static object o = new object(); private static int tickets = 100; static void Main(string[] args) { Thread thread = BuyTicket(); Thread thread2 = BuyTicket(); Thread thread3 = BuyTicket(); thread.Name = "Thread1"; thread2.Name = "Thread2"; thread3.Name = "Thread3"; thread.Start(); thread2.Start(); thread3.Start(); Thread.Sleep(5000); } private static Thread BuyTicket() { Thread thread = new Thread(() => { while (true) { lock (o) { if (tickets > 0) { Console.WriteLine($"{Thread.CurrentThread.Name}---------------->買了一張票,票號為:{tickets}"); tickets--; } } } }); thread.IsBackground = true; return thread; }
在while塊中,我加上了一個lock塊,它需要一個Object型別的引數作為同步物件,被lock塊包住的程式碼,在同一時間只能有一個執行緒訪問,看一下執行結果(方便檢視,我將數量改為了30):
可以看到,執行緒安全問題已經解決。我們再來看一下同步物件:
lock (object obj){}
lock塊,它需要一個object型別的引數作為同步物件,也就是說,執行緒走到這裡,會先看看這個同步物件是不是被佔用著,如未被佔用,則進入,否則執行緒阻塞,直到同步物件被解除佔用,注意,多個執行緒,要使用一個同步物件,不然,一個執行緒訪問一個單獨的同步物件,那跟沒加鎖一樣,另外,根據多型性,這個同步物件可以是任意物件,因為object是所有類的父類,但是string型別不可用,這點要注意。
Monitor鎖
monitor鎖的用法跟lock差不多,請看如下程式碼:
while (true) { Monitor.Enter(o); if (tickets > 0) { Console.WriteLine($"{Thread.CurrentThread.Name}---------------->買了一張票,票號為:{tickets}"); tickets--; } Monitor.Exit(o); }
monitor將程式碼塊改為了enter和exit兩個方法,也是需要同步物件。
Mutex互斥鎖
互斥鎖是一個互斥的同步物件,同一時間有且僅有一個執行緒可以獲取它。跟monitor一樣,也是通過兩個方法控制的,具體用法請看下面的程式碼:
private static Mutex mutex = new Mutex(); private static Thread BuyTicket3() { Thread thread = new Thread(() => { while (true) { mutex.WaitOne();//等待 if (tickets > 0) { Console.WriteLine($"{Thread.CurrentThread.Name}---------------->買了一張票,票號為:{tickets}"); tickets--; } mutex.ReleaseMutex();//解除 } }); thread.IsBackground = true; return thread; }
死鎖
如果濫用執行緒鎖,容易出現死鎖的問題,什麼是死鎖呢?比如有兩個執行緒T1,T2,它們共用兩個同步鎖L1,L2,T1先走L1,T2先走L2,T1下一步走L2,T2下一步走l1,這樣這兩個執行緒各種握著對方的下一步鎖,一直阻塞最後誰也走不了。或者使用像monitor這樣的鎖,突然出現異常,Exit方法來不及執行,也會死鎖,其它的執行緒也會一直阻塞。下面來演示一下:
private static Thread BuyTicket2() { Thread thread = new Thread(() => { try { while (true) { Monitor.Enter(o); throw new Exception("THREAD DEAD!"); if (tickets > 0) { Console.WriteLine($"{Thread.CurrentThread.Name}---------------->買了一張票,票號為:{tickets}"); tickets--; } Monitor.Exit(o); } } catch{} }); thread.IsBackground = true; return thread; }
執行結果如下:
因為一開始執行緒就直接死鎖,其它的執行緒無法繼續執行,會一直阻塞。
這是我的公眾號二維碼,獲取最新文章,請關注此號