如何避免死鎖和活鎖? - simar
死鎖只能在併發(多執行緒)程式中發生,其中同步(使用鎖)執行緒訪問一個或多個共享資源(變數和物件)或指令集(臨界區)。
活鎖時當我們試圖避免死鎖時會使用非同步鎖定時發生的,其中多個執行緒對同一組鎖的競爭寫操作,為了避免獲取鎖定,允許其他執行緒第一個到達的獲得鎖,等待最終釋放鎖定後再繼續,這容易造成等待執行緒不斷重試獲取鎖造成的CPU迴圈飢餓。非同步鎖只是一種避免死鎖成為活鎖的策略。
下面是一些的理論上解決死鎖的方法,並且其中之一(第二個)是主要的原因為活鎖。
理論方法
1. 不要使用鎖
在兩個操作需要同步的情況下是不可能的,例如,簡單的銀行轉帳,您可以借記一個帳戶然後可以貸記另一個帳戶,並且在當前執行緒完成之前不允許任何其他執行緒觸及帳戶中的餘額。
2.不要阻塞鎖,如果一個執行緒無法獲取鎖,它應該釋放以前獲取的鎖,以便稍後再試
實施起來很麻煩並且可能導致飢餓(活鎖),執行緒總是重試獲取鎖。此外,這種方法可能在頻繁的執行緒上下文切換中會造成切換開銷,從而降低了系統的整體效能。
3. 讓執行緒始終以嚴格的順序請求鎖定
說起來容易做起來難。如果我們正在寫一個函式將賬戶A的資金轉移到B,我們可以寫一些類似的東西:
// at compile time, we take lock on first arg then second public void transfer(Account A, Account B, long money) { synchronized (A) { synchronized (B) { A.add(amount); B.subtract(amount); } } } // at runtime we cannot track how our methods will be called public void run() { new Thread(()->this.transfer(X,Y, 10000)).start(); new Thread(()->this.transfer(Y,X, 10000)).start(); } // this run() will create a deadlock // first thread locks on X, waits for Y // second thread locks on Y, waits for X |
現實中的解決方案
我們可以結合鎖定順序和定時鎖定的方法來得到真正現實解決方案
1. 透過業務確定鎖的順序
我們可以透過根據帳號大小區分A和B來改進我們的方法。
// at run time, we take lock on account with smaller id first public void transfer(Account A, Account B, long money) { final Account first = A.id < B.id ? A : B; final Account second = first == A? B: A; synchronized (first) { synchronized (second) { first.add(amount); second.subtract(amount); } } } // at runtime we cannot track how our methods will be called public void run() { new Thread(()->this.transfer(X,Y, 10000)).start(); new Thread(()->this.transfer(Y,X, 10000)).start(); } |
如果X.id = 1111和Y.id = 2222,因為我們採取的第一個帳戶為一個較小的賬戶ID,鎖定順序執行:transfer(Y, X, 10000)和transfer(X,Y, 10000)將是一樣的。如果X的帳號小於Y,則兩個執行緒將嘗試在Y之前鎖定X,並且只有X成功後才繼續鎖定Y。
2. 業務確定tryLock / async 的時間等待的鎖請求
使用上述業務確定性鎖順序的解決方案僅適用於一個地方的邏輯轉移(...)的關聯關係,例如在我們的方法中確定如何協調資源。
我們最終可能會有其他方法/邏輯,最終使用與之不相容的排序邏輯transfer(…)。為避免在這種情況下出現死鎖,建議使用非同步鎖定,我們嘗試鎖定資源的有限/實際時間(最大事務時間)+小隨機等待時間,這樣所有執行緒都不會嘗試分別獲得太早而避免了活鎖(由於無法獲取鎖反覆嘗試而導致飢餓)
// assume AccountgetLock() gives us account's Lock (java.util.concurrent.locks.Lock) // Account could encapsulate lock, provide lock() /unlock() public long getWait() { /// returns moving average of transfer times for last n transfers + small-random-salt in millis so all threads waiting to lock do not wake up at the same time. //////返回最後n次傳輸的傳輸時間的移動平均值+ 小隨機時間,因此等待鎖定的所有執行緒不會同時喚醒。 |
} public void transfer(Lock lockF, Lock lockS, int amount) { final Account first = A.id < B.id ? A : B; final Account second = first == A? B: A; final Lock lockF = first.getLock(); final Lock lockS = second.getLock(); boolean done = false; do { try { try { if (lockF.tryLock(getWait(), MILLISECONDS)) { try { if (lockS.tryLock(getWait(), MILLISECONDS)) { done = true; } } finally { lockS.unlock(); } } } catch (InterruptedException e) { throw new RuntimeException("Cancelled"); } } finally { lockF.unlock(); } } while (!done); } // at runtime we cannot track how our methods will be called public void run() { new Thread(()->this.transfer(X,Y, 10000)).start(); new Thread(()->this.transfer(Y,X, 10000)).start(); } |
相關文章
- 死鎖是什麼?如何預防和避免死鎖?
- java如何避免程式死鎖Java
- 面試:什麼是死鎖,如何避免或解決死鎖;MySQL中的死鎖現象,MySQL死鎖如何解決面試MySql
- [C#.NET 拾遺補漏]12:死鎖和活鎖的發生及避免C#
- 鎖的使用與死鎖的避免
- 面試官:什麼是死鎖?怎麼排查死鎖?怎麼避免死鎖?面試
- 如何避免死鎖?我們有套路可循
- [Java併發]避免死鎖Java
- MySQL 死鎖和鎖等待MySql
- 死鎖和可重入鎖
- 什麼是死鎖?如何解決死鎖?
- 作業系統(5) 死鎖的概念 死鎖產生的必要條件 死鎖的處理策略 預防死鎖 避免死鎖 死鎖的檢測和解除 銀行家演算法作業系統演算法
- Java中常見死鎖與活鎖的例項Java
- Mysql 兩階段鎖和死鎖MySql
- mysql行鎖和死鎖檢測MySql
- 死鎖避免中的安全狀態和不安全狀態
- 死鎖
- 在 SQL Server 中查詢活動連線和死鎖SQLServer
- MySQL:MTS和mysqldump死鎖MySql
- Mysql如何處理死鎖MySql
- java多執行緒中的死鎖、活鎖、飢餓、無鎖都是什麼鬼?Java執行緒
- 死鎖概述
- 阿里二面:如何定位&避免死鎖?連著兩個面試問到了!阿里面試
- SQL SERVER死鎖查詢,死鎖分析,解鎖,查詢佔用SQLServer
- 例項詳解 Java 死鎖與破解死鎖Java
- 檢視oracle死鎖程式並結束死鎖Oracle
- MySQL死鎖系列-線上死鎖問題排查思路MySql
- SQLServer的死鎖分析(1):頁鎖SQLServer
- 併發程式設計之臨界區\阻塞\非阻塞\死鎖\飢餓\活鎖程式設計
- 死鎖-舉例
- 併發:死鎖
- 遭遇ITL死鎖
- 死鎖案例分析
- GCD 死鎖原因GC
- 死鎖案例二
- 死鎖案例三
- MySQL鎖:InnoDB行鎖需要避免的坑MySql
- 如何處理執行緒死鎖執行緒