轉自 http://www.cnblogs.com/tianma3798/p/6290158.html
參考官網 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement
一、lock關鍵詞說明
1. lock 關鍵字將語句塊標記為臨界區,方法是獲取給定對象的互斥鎖,執行語句,然后釋放該鎖。
2. lock 語句塊鎖定,功能等同於
Monitor.Enter(obj); //代碼段 Monitor.Exit(obj);
3. lock語句塊鎖定和Monitor線程鎖,不能跨進程同步
lock
關鍵字可確保當一個線程位於代碼的臨界區時,另一個線程不會進入該臨界區。 如果其他線程嘗試進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。
線程 這節討論了線程處理。
lock
關鍵字在塊的開始處調用 Enter,而在塊的結尾處調用 Exit。 ThreadInterruptedException 引發,如果 Interrupt 中斷等待輸入 lock
語句的線程。
通常,應避免鎖定 public
類型,否則實例將超出代碼的控制范圍。 常見的結構 lock (this)
、lock (typeof (MyType))
和 lock ("myLock")
違反此准則:
-
如果實例可以被公共訪問,將出現
lock (this)
問題。 -
如果
MyType
可以被公共訪問,將出現lock (typeof (MyType))
問題。 -
由於進程中使用同一字符串的任何其他代碼都將共享同一個鎖,所以出現
lock("myLock")
問題。
最佳做法是定義 private
對象來鎖定, 或 private static
對象變量來保護所有實例所共有的數據。
在 lock
語句的正文不能使用 等待 關鍵字。
三、特別說明
1.lock語句中鎖定的必須是引用類型的對象,不能是值類型
(1)值類型 一般都在線程函數自己的棧里,每個線程局部棧是不一樣的,互相之間不會有影響,所以不用鎖定
一個特例,引用類型值類型字段在堆里,但可以通過lock那個引用類型對象就可以實現了。
(2)引用可以指向同一個對象,而值類型的變量每次都是不同的
(3)他要的就是引用類型,如果你傳一個值類型,會裝箱,下次代碼運行到這里,又會裝箱,兩次不是同一個對象,所以鎖不住。這個解釋最靠譜
2.為了避免死鎖,lock的對象需要是private對象
3.為了避免lock對象的唯一性,通lock的對象為 private static或者 private readonly static
第二章,什么時候需要鎖
轉自 http://www.cnblogs.com/plin2008/archive/2009/06/24/1510057.html
首先要理解鎖定是解決競爭條件的,也就是多個線程同時訪問某個資源,造成意想不到的結果。比如,最簡單的情況是,一個計數器,兩個線程 同時加一,后果就是損失了一個計數,但相當頻繁的鎖定又可能帶來性能上的消耗,還有最可怕的情況死鎖。那么什么情況下我們需要使用鎖,什么情況下不需要 呢?
1)只有共享資源才需要鎖定
只有可以被多線程訪問的共享資源才需要考慮鎖定,比如靜態變量,再比如某些緩存中的值,而屬於線程內部的變量不需要鎖定。
2)多使用lock,少用Mutex
如果你一定要使用鎖定,請盡量不要使用內核模塊的鎖定機制,比如.NET的Mutex,Semaphore,AutoResetEvent和 ManuResetEvent,使用這樣的機制涉及到了系統在用戶模式和內核模式間的切換,性能差很多,但是他們的優點是可以跨進程同步線程,所以應該清 楚的了解到他們的不同和適用范圍。
3)了解你的程序是怎么運行的
實際上在web開發中大多數邏輯都是在單個線程中展開的,一個請求都會在一個單獨的線程中處理,其中的大部分變量都是屬於這個線程的,根本沒有必要考慮鎖定,當然對於ASP.NET中的Application對象中的數據,我們就要考慮加鎖了。
4)把鎖定交給數據庫
數 據庫除了存儲數據之外,還有一個重要的用途就是同步,數據庫本身用了一套復雜的機制來保證數據的可靠和一致性,這就為我們節省了很多的精力。保證了數據源 頭上的同步,我們多數的精力就可以集中在緩存等其他一些資源的同步訪問上了。通常,只有涉及到多個線程修改數據庫中同一條記錄時,我們才考慮加鎖。
5)業務邏輯對事務和線程安全的要求
這 條是最根本的東西,開發完全線程安全的程序是件很費時費力的事情,在電子商務等涉及金融系統的案例中,許多邏輯都必須嚴格的線程安全,所以我們不得不犧牲 一些性能,和很多的開發時間來做這方面的工作。而一般的應用中,許多情況下雖然程序有競爭的危險,我們還是可以不使用鎖定,比如有的時候計數器少一多一, 對結果無傷大雅的情況下,我們就可以不用去管它。
三、小題練練
轉自 http://www.fx114.net/qa-276-77022.aspx
看看以下會不會產生死鎖:
public class A { private object obj = new object(); public void Test(int i) { lock (obj) { if (i>10) { i--; Test(i); } else { Console.WriteLine(i); } } } } class Program { static void Main(string[] args) { A a = new A(); a.Test(15); Console.ReadKey(); } }
你的答案是會產生死鎖嗎?可以理解,因為也許你以前一直以為外層遞歸被鎖住后,不允許在訪問里邊的代碼快,而由於遞歸,有需要等待外層遞歸解鎖,所以由此造成死鎖,現在才知道這種理解是錯誤的;結果是永遠不會出現死鎖,因為lock的本質是對於不同的線程來說的,或者這個所的所有權已經歸該現成所有,它可以任意使用該鎖;
說道這里又想多說一點,假如另一個線程在另一個函數里操作同一個obj,這是obj可以被訪問嗎在鎖釋放之前,如下代碼:
public class Test { string name = "Empty"; public Test() { } public string Name { get { return this.name; } set { this.name = value; } } } public class A { Test test = new Test(); public void OneMethod() { lock (this.test) { Console.WriteLine("one thread come in,sleep for 10 seconds"); Console.WriteLine(test.Name); Thread.Sleep(10000);//如果第二個線程可以在鎖住期間可以對test對象操作,那么線程二應該在10000毫秒之內已經輸出 test.Name = "One thread output"; Console.WriteLine(test.Name); } } public void TwoMethod() { Thread.Sleep(100);//為了確保第一個線程先鎖住后在操作test對象 Console.WriteLine("two thread come in");//假如由於線程一鎖住test對象而不能對test操作的話,那么要等待10000毫秒 test.Name = "Two thread output"; Console.WriteLine(test.Name); } } class Program { static void Main(string[] args) { A a = new A(); Thread one = new Thread(new ThreadStart(a.OneMethod)); Thread two = new Thread(new ThreadStart(a.TwoMethod)); one.Start(); two.Start(); Console.ReadKey(); } } 其輸出結果為: one thread come in,sleep for 10 seconds Empty two thread come in//在第一個線程之間操作 Two thread output//在第一個線程鎖住test對象之中,改變的test。Name屬性值,說明鎖中test時將可以對test進行操作 One thread output
答案是:test對象在別的函數中被鎖中時,別的線程在被鎖塊外是可以對test進行操作的
假如我把第二個方法代碼做如下修改:
public void TwoMethod() { Thread.Sleep(100);//為了確保第一個線程先鎖住后在操作test對象 Console.WriteLine("two thread come in");//假如由於線程一鎖住test對象而不能對test操作的話,那么要等待10000毫秒 lock (this.test) { test.Name = "Two thread output"; Console.WriteLine(test.Name); } } 這時的結果是: one thread come in,sleep for 10 seconds Empty two thread come in//在第一個線程獲得鎖之間線程二進入了第二方法中的鎖之前操作 One thread output// Twothread output//說明鎖test被解鎖以后才執行
答案是:一個類中幾個方法鎖住了同一個對象(該對象必須是引用類型)時,那么第一個獲得鎖的線程操作,其他線程走到鎖對象(哪怕所對象不是在同一個函數中)之前將等待對象被解鎖,解鎖后才可以進入。打個比喻:test對象相當於一個響應器,lock()鎖相當於一個鎖,操作系統相當於一個監控器,而一個或多個函數中的鎖塊相當於一個或多大門,多個線程相當於等在一個或多個大門前的人。假如有任一一個大門被一個人闖進時,那么監控器立即給所有鎖上鎖lock,鎖定所有大門,不准任何人進,直到該人出來,監控器自動打開所有鎖,所有大門打開,重復執行第一步,監控器保證只有一個人進入某一個大門。當然如果某一個響應器沒有lock,就相當於大門沒有鎖,就可以對test操作,這種情況就是上一種現象。
二、一道面試題(目前我也不太明白)
轉自 https://www.cnblogs.com/myshell/archive/2010/07/18/1780386.html
和https://q.cnblogs.com/q/47290/
public void test(int i) { lock(this) { if (i > 10) { i--; test(i); } } }
分析一下代碼,當調用test方法時i>10時是否會引起死鎖
不會。終於在《CLR via C#》第二版(中文版,清華大學出版社出版)的第530頁中第7行找到了這樣的描述:“同樣需要引起注意的是線程可以遞歸擁有同步塊”。即同一線程可以遞歸調用lock語句,這是單線程操作,該線程本身就是鎖的擁有者,當再次進入時,自然能獲取到進入該鎖塊的權利。
線程重入問題,lock會與線程關聯,如關聯上A1線程,那么A1線程重復進入多次也是OK的。但是lock又是獨占鎖,所以一旦關聯上線程,別的線程就無法進入lock代碼塊。
多線程操作時,會阻塞等待。