在多線程編程裏面我們經常會鎖的使用,實際上在這個過程中,我們的鎖主要分爲悲觀鎖和樂觀鎖,用他們來實現多線程的併發編程控制。
悲觀鎖:
我們悲觀的認爲這一段代碼或者是數據會發生資源搶佔和異常,那麼我們在進入這一段代碼或者訪問某一個數據之前就直接先加上鎖,我訪問的東西,其他人都不可以再訪問,除非我訪問完了,退出此處加鎖資源。例如我們常見的Lock、Monitor、Mutex等
Lock :實際上是對Monitor的封裝,一個語法糖。
try { Monitor.Enter(obj); dosomething(); } catch(Exception ex) { } finally { Monitor.Exit(obj); }
Monitor:
Monitor的常用屬性和方法:
Enter(Object) 獲取排他鎖。
Exit(Object) 釋放排他鎖。
IsEntered 當前線程是否獲取排它鎖鎖。
Wait(Object) 釋放對象上的鎖到等待隊列並阻止當前線程,通知下一個就緒隊列的線程進來。
Pulse() 當前線程不釋放鎖,但是通知隊列中的下一個鎖進入就緒隊列。
PulseAll() 通知所有的等待線程對象狀態的更改。
TryEnter(Object) 試圖獲取指定對象的排他鎖。
TryEnter(Object, Boolean) 嘗試獲取指定對象上的排他鎖,並自動設置一個值,指示是否得到了該鎖。
Mutex: 互斥鎖,每次只能一個線程能夠訪問指定對象。
樂觀鎖:
我們樂觀的認爲不會發生線程不安全的問題,我們不會加鎖,直到需要更新的時候再去檢查我們的數據有沒有被修改,如果沒有修改,那麼我們這邊直接修改,如果有過修改,那麼返回錯誤等用戶判斷如何處理。
實現樂觀鎖有兩種方法:1. CAS算法。2.版本Version。
1. CAS算法Compare And Swap(比較與交換),要點是需要讀寫的內存值 D, 進行比較的值 E, 擬寫入的新值 F,如果D=E,那麼寫入F。CAS算法有一個ABA問題,就是我們需要進行比較的值E,我們認爲它一直是E,但是可能它由E變爲M,然後變更爲E,我們認爲它還是E但是實際上它中途被變更過,爲了解決這個問題我們引入了第二種方案進行控制那就是版本.
2. Version版本控制,每次我們操作上面提到的E值時,我們都將Version增加1,這樣子我們去判斷Version是否我變更前的版本一致由此來看是否能夠提交此次修改。在C#中我們可以考慮寫一個類裏面增加一個Version字段來達成樂觀鎖,或者在數據庫裏面新增一列爲Version來控制。
自旋鎖SpinLock:
自旋鎖是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖纔會退出循環。