Java死鎖避免的五種方法舉例總結
前言
死鎖(Deadlock)是指多個線程互相持有對方需要的資源,導致所有線程都無法繼續(xù)執(zhí)行的情況。Java 中可以通過以下方法避免死鎖:
造成死鎖的?個原因:
?個資源每次只能被?個線程使?
?個線程在阻塞等待某個資源時,不釋放已占有資源
?個線程已經(jīng)獲得的資源,在未使?完之前,不能被強?剝奪
若?線程形成頭尾相接的循環(huán)等待資源關系
這是造成死鎖必須要達到的4個條件,如果要避免死鎖,只需要不滿?其中某?個條件即可。?其中前3 個條件是作為鎖要符合的條件,所以要避免死鎖就需要打破第4個條件,不出現(xiàn)循環(huán)等待鎖的關系。
在開發(fā)過程中:
要注意加鎖順序,保證每個線程按同樣的順序進?加鎖
要注意加鎖時限,可以針對所設置?個超時時間
要注意死鎖檢查,這是?種預防機制,確保在第?時間發(fā)現(xiàn)死鎖并進?解決
1. 死鎖產生的四個必要條件
要避免死鎖,首先要理解死鎖發(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ā)度降低(所有轉賬操作串行化)。
方法 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è)務邏輯
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (gotLock1) lock1.unlock();
if (gotLock2) lock2.unlock();
}
}方法 5:檢測與恢復(允許死鎖發(fā)生,但能自動恢復)
適用于復雜系統(tǒng)(如數(shù)據(jù)庫、分布式系統(tǒng)):
1.檢測死鎖:
使用 ThreadMXBean 查找死鎖:
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
System.out.println("檢測到死鎖!");
}2.恢復策略:
強制終止某個線程(如 thread.interrupt())。
回滾事務(數(shù)據(jù)庫場景)。
3. 死鎖案例分析
? 錯誤代碼(會導致死鎖)
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)結果:互相等待,死鎖!
4. 總結
| 方法 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|
| 固定順序加鎖 | 簡單鎖依賴 | 實現(xiàn)簡單 | 需要全局排序規(guī)則 |
| tryLock + 超時 | 高并發(fā)系統(tǒng) | 避免無限等待 | 代碼復雜度高 |
| 全局鎖 | 低并發(fā)場景 | 絕對安全 | 性能差(串行化) |
| Lock 替代 synchronized | 需要更細粒度控制 | 靈活(可中斷、超時) | 需手動釋放鎖 |
| 檢測與恢復 | 數(shù)據(jù)庫、分布式系統(tǒng) | 適用于復雜場景 | 實現(xiàn)復雜 |
最佳實踐:
盡量用 tryLock + 超時(推薦
ReentrantLock)。如果必須用 synchronized,按固定順序加鎖。
避免嵌套鎖(如
synchronized里再調synchronized方法)。使用工具檢測死鎖(如
jstack、ThreadMXBean)。
到此這篇關于Java死鎖避免的五種方法的文章就介紹到這了,更多相關Java死鎖避免內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot 項目中的圖片處理策略之本地存儲與路徑映射
在SpringBoot項目中,靜態(tài)資源存放在static目錄下,使得前端可以通過URL來訪問這些資源,我們就需要將文件系統(tǒng)的文件路徑與URL建立一個映射關系,把文件系統(tǒng)中的文件當成我們的靜態(tài)資源即可,本文給大家介紹SpringBoot本地存儲與路徑映射的相關知識,感興趣的朋友一起看看吧2023-12-12
解決springboot 獲取form-data里的file文件的問題
這篇文章主要介紹了解決springboot 獲取form-data里的file文件的問題的相關資料,這里提供了詳細的解決步驟,需要的朋友可以參考下2017-07-07
使用springMVC通過Filter實現(xiàn)防止xss注入
這篇文章主要介紹了使用springMVC通過Filter實現(xiàn)防止xss注入的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Springboot項目的搭建教程(分離出common父依賴)
這篇文章主要介紹了Springboot項目的搭建教程(分離出common父依賴),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01

