Java死鎖避免的五種方法舉例總結(jié)
前言
死鎖(Deadlock)是指多個線程互相持有對方需要的資源,導(dǎo)致所有線程都無法繼續(xù)執(zhí)行的情況。Java 中可以通過以下方法避免死鎖:
造成死鎖的?個原因:
?個資源每次只能被?個線程使?
?個線程在阻塞等待某個資源時,不釋放已占有資源
?個線程已經(jīng)獲得的資源,在未使?完之前,不能被強?剝奪
若?線程形成頭尾相接的循環(huán)等待資源關(guān)系
這是造成死鎖必須要達到的4個條件,如果要避免死鎖,只需要不滿?其中某?個條件即可。?其中前3 個條件是作為鎖要符合的條件,所以要避免死鎖就需要打破第4個條件,不出現(xiàn)循環(huán)等待鎖的關(guān)系。
在開發(fā)過程中:
要注意加鎖順序,保證每個線程按同樣的順序進?加鎖
要注意加鎖時限,可以針對所設(shè)置?個超時時間
要注意死鎖檢查,這是?種預(yù)防機制,確保在第?時間發(fā)現(xiàn)死鎖并進?解決
1. 死鎖產(chǎn)生的四個必要條件
要避免死鎖,首先要理解死鎖發(fā)生的條件(必須全部滿足):
互斥條件:資源一次只能被一個線程占用。
占有并等待:線程持有資源的同時,等待其他資源。
不可搶占:線程持有的資源不能被其他線程強行奪走。
循環(huán)等待:多個線程形成環(huán)形等待鏈(A 等 B,B 等 C,C 等 A)。
只要破壞其中任意一個條件,就能避免死鎖!
2. 避免死鎖的 5 種方法
方法 1:按固定順序獲取鎖(破壞循環(huán)等待)
核心思想:所有線程按相同的順序申請鎖,避免循環(huán)依賴。
? 正確示例:
public void transfer(Account from, Account to, int amount) { // 規(guī)定:先鎖 id 小的賬戶,再鎖 id 大的賬戶 Account first = from.getId() < to.getId() ? from : to; Account second = from.getId() < to.getId() ? to : from; synchronized (first) { synchronized (second) { if (from.getBalance() >= amount) { from.debit(amount); to.credit(amount); } } } }
為什么能避免死鎖?所有線程都按 id
順序加鎖,不會出現(xiàn) A 鎖 1 → 等 2
和 B 鎖 2 → 等 1
的情況。
方法 2:使用 tryLock + 超時(破壞占有并等待)
核心思想:如果拿不到鎖,就釋放已持有的鎖,避免無限等待。
? 正確示例(ReentrantLock):
public boolean transfer(Account from, Account to, int amount, long timeout) throws InterruptedException { long startTime = System.currentTimeMillis(); while (true) { if (from.lock.tryLock()) { // 嘗試獲取第一把鎖 try { if (to.lock.tryLock()) { // 嘗試獲取第二把鎖 try { if (from.getBalance() >= amount) { from.debit(amount); to.credit(amount); return true; } } finally { to.lock.unlock(); } } } finally { from.lock.unlock(); // 釋放第一把鎖 } } // 超時檢查 if (System.currentTimeMillis() - startTime >= timeout) { return false; } Thread.sleep(100); // 避免 CPU 忙等待 } }
優(yōu)點:
不會無限等待,超時后可以重試或回滾。
適用于高并發(fā)場景(如支付系統(tǒng))。
方法 3:一次性申請所有資源(破壞占有并等待)
核心思想:使用一個全局鎖,一次性申請所有需要的資源。
? 正確示例:
public class AccountManager { private static final Object globalLock = new Object(); public void transfer(Account from, Account to, int amount) { synchronized (globalLock) { // 全局鎖保護所有賬戶 if (from.getBalance() >= amount) { from.debit(amount); to.credit(amount); } } } }
缺點:并發(fā)度降低(所有轉(zhuǎn)賬操作串行化)。
方法 4:使用 Lock 替代 synchronized(更靈活的控制)
ReentrantLock
比 synchronized
更靈活,可以避免死鎖:
Lock lock1 = new ReentrantLock(); Lock lock2 = new ReentrantLock(); public void methodA() { boolean gotLock1 = false; boolean gotLock2 = false; try { gotLock1 = lock1.tryLock(1, TimeUnit.SECONDS); gotLock2 = lock2.tryLock(1, TimeUnit.SECONDS); if (gotLock1 && gotLock2) { // 執(zhí)行業(yè)務(wù)邏輯 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (gotLock1) lock1.unlock(); if (gotLock2) lock2.unlock(); } }
方法 5:檢測與恢復(fù)(允許死鎖發(fā)生,但能自動恢復(fù))
適用于復(fù)雜系統(tǒng)(如數(shù)據(jù)庫、分布式系統(tǒng)):
1.檢測死鎖:
使用 ThreadMXBean
查找死鎖:
ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.findDeadlockedThreads(); if (threadIds != null) { System.out.println("檢測到死鎖!"); }
2.恢復(fù)策略:
強制終止某個線程(如 thread.interrupt()
)。
回滾事務(wù)(數(shù)據(jù)庫場景)。
3. 死鎖案例分析
? 錯誤代碼(會導(dǎo)致死鎖)
public void transfer(Account from, Account to, int amount) { synchronized (from) { // 線程1:鎖 from → 等 to synchronized (to) { // 線程2:鎖 to → 等 from if (from.getBalance() >= amount) { from.debit(amount); to.credit(amount); } } } }
死鎖場景:
線程1:
transfer(accountA, accountB, 100)
線程2:
transfer(accountB, accountA, 200)
結(jié)果:互相等待,死鎖!
4. 總結(jié)
方法 | 適用場景 | 優(yōu)點 | 缺點 |
---|---|---|---|
固定順序加鎖 | 簡單鎖依賴 | 實現(xiàn)簡單 | 需要全局排序規(guī)則 |
tryLock + 超時 | 高并發(fā)系統(tǒng) | 避免無限等待 | 代碼復(fù)雜度高 |
全局鎖 | 低并發(fā)場景 | 絕對安全 | 性能差(串行化) |
Lock 替代 synchronized | 需要更細粒度控制 | 靈活(可中斷、超時) | 需手動釋放鎖 |
檢測與恢復(fù) | 數(shù)據(jù)庫、分布式系統(tǒng) | 適用于復(fù)雜場景 | 實現(xiàn)復(fù)雜 |
最佳實踐:
盡量用 tryLock + 超時(推薦
ReentrantLock
)。如果必須用 synchronized,按固定順序加鎖。
避免嵌套鎖(如
synchronized
里再調(diào)synchronized
方法)。使用工具檢測死鎖(如
jstack
、ThreadMXBean
)。
到此這篇關(guān)于Java死鎖避免的五種方法的文章就介紹到這了,更多相關(guān)Java死鎖避免內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Java的Struts中判斷是否調(diào)用AJAX及用攔截器對其優(yōu)化
這篇文章主要介紹了在Java的Struts中判斷是否調(diào)用AJAX及用攔截器對其優(yōu)化的方法,Struts框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2016-01-01springcloud連接遠程nacos失敗顯示localhost服務(wù)連接失敗的問題解決
這篇文章主要介紹了springcloud連接遠程nacos失敗顯示localhost服務(wù)連接失敗的問題解決,文中有詳細的代碼示例供大家參考,對大家解決問題有一定的幫助,需要的朋友可以參考下2024-03-03