Java?并發(fā)編程之深入理解"鎖可中斷"機制
在 Java 并發(fā)編程中,死鎖(Deadlock)和線程阻塞(Blocking)是開發(fā)者最頭疼的問題之一。當一個線程無限期地等待一個鎖時,整個系統(tǒng)可能會陷入停滯。
為了解決這個問題,Java 提供了 java.util.concurrent.locks.Lock 接口,其中有一個關(guān)鍵方法:lockInterruptibly()。它實現(xiàn)了 “鎖可中斷” 的特性。
1. 什么是“鎖可中斷”?
“鎖可中斷” 指的是:當一個線程在等待獲取鎖的過程中,如果收到了中斷信號(interrupt),它可以放棄等待,拋出 InterruptedException 異常,從而結(jié)束阻塞狀態(tài),而不是無限期地傻等下去。
這是 ReentrantLock 等顯式鎖相對于內(nèi)置鎖 synchronized 的一個重大優(yōu)勢,它賦予了開發(fā)者主動控制線程等待行為的能力。
核心對比
| 特性 | synchronized | ReentrantLock.lock() | ReentrantLock.lockInterruptibly() |
|---|---|---|---|
| 鎖類型 | 內(nèi)置鎖 (隱式) | 顯式鎖 | 顯式鎖 |
| 等待鎖時響應中斷 | ? 不支持 | ? 不支持 | ? 支持 |
| 行為描述 | 線程會一直死等,忽略中斷信號,直到拿到鎖。 | 同 synchronized,一直死等。 | 收到中斷信號后,停止等待,拋出異常。 |
| 靈活性 | 低 | 中 | 高 |
2. 代碼實戰(zhàn):等待鎖時的中斷
下面是一個演示“鎖可中斷”的經(jīng)典場景。線程 A 持有鎖,線程 B 嘗試獲取鎖。主線程隨后中斷線程 B。
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 1. 線程 A 獲取鎖,并長時間持有
Thread threadA = new Thread(() -> {
lock.lock();
try {
System.out.println("Thread A: 獲取了鎖,開始執(zhí)行長任務(wù)...");
Thread.sleep(100000); // 模擬長時間占用
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
// 2. 線程 B 嘗試獲取鎖,使用 lockInterruptibly()
Thread threadB = new Thread(() -> {
try {
System.out.println("Thread B: 嘗試獲取鎖...");
// 關(guān)鍵點:使用可中斷的獲取鎖方法
lock.lockInterruptibly();
try {
System.out.println("Thread B: 成功獲取鎖!");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 3. 捕獲中斷異常
System.out.println("Thread B: 等待鎖時被中斷了!" + e.getMessage());
}
});
threadA.start();
Thread.sleep(1000); // 確保 A 先拿到鎖
threadB.start();
Thread.sleep(1000); // 確保 B 進入等待狀態(tài)
// 4. 主線程中斷線程 B
System.out.println("Main: 準備中斷 Thread B...");
threadB.interrupt();
}
}輸出結(jié)果:
Thread A: 獲取了鎖,開始執(zhí)行長任務(wù)...
Thread B: 嘗試獲取鎖...
Main: 準備中斷 Thread B...
Thread B: 等待鎖時被中斷了!java.lang.InterruptedException
結(jié)論: 線程 B 沒有死等線程 A 釋放鎖,而是響應了中斷信號,提前退出了等待。
3. 核心誤區(qū):持有鎖時能被中斷嗎?
這是很多開發(fā)者容易混淆的地方。“鎖可中斷”僅針對“等待獲取鎖”的階段,而不是“已經(jīng)持有鎖”的階段。
場景分析
- 等待鎖時(Waiting for Lock):
- 調(diào)用
lockInterruptibly()后,鎖被占用,線程阻塞。 - 此時
interrupt()-> 線程立即醒來,拋出異常,放棄獲取鎖。
- 調(diào)用
- 持有鎖時(Holding Lock):
- 線程已經(jīng)拿到了鎖,正在執(zhí)行臨界區(qū)代碼。
- 此時
interrupt()-> 線程的中斷標志位變?yōu)?true,但線程不會停止,鎖也不會自動釋放。 - 線程會繼續(xù)執(zhí)行,直到代碼自然結(jié)束或遇到其他可中斷阻塞(如
sleep)。
為什么持有鎖時不強制釋放?
這是為了數(shù)據(jù)安全。
假設(shè)線程 A 持有鎖,正在執(zhí)行一個多步操作(如:讀取余額 -> 計算利息 -> 寫入余額)。如果在中途強制中斷并釋放鎖:
- 線程 A 可能只執(zhí)行了“讀取”,還沒“寫入”。
- 鎖被釋放,線程 B 介入,讀到了不一致的中間狀態(tài)數(shù)據(jù)。
- 導致數(shù)據(jù)臟讀或邏輯錯誤。
因此,Java 的設(shè)計原則是:中斷只是“建議”線程停止,持有鎖的線程必須自己決定何時安全地退出,并在 finally 塊中手動釋放鎖。
代碼驗證:持有鎖時無視中斷
// 簡化的邏輯演示
Thread worker = new Thread(() -> {
lock.lock(); // 獲取鎖
try {
// 執(zhí)行任務(wù),即使此時被 interrupt,也會繼續(xù)執(zhí)行
for (int i = 0; i < 3; i++) {
if (Thread.interrupted()) {
System.out.println("Worker: 發(fā)現(xiàn)中斷標志,但我持有鎖,繼續(xù)執(zhí)行...");
}
System.out.println("Worker: 執(zhí)行步驟 " + i);
}
} finally {
lock.unlock(); // 必須手動釋放
System.out.println("Worker: 鎖已釋放。");
}
});4. 應用場景
既然 synchronized 更簡單,為什么還要用可中斷鎖?主要適用于以下場景:
- 避免死鎖(Deadlock Avoidance):
- 如果系統(tǒng)檢測到死鎖風險,可以通過中斷其中一個等待鎖的線程,讓它回退并釋放已持有的資源,從而打破死鎖循環(huán)。
- 任務(wù)取消(Task Cancellation):
- 用戶在前端點擊了“取消”按鈕,后端需要停止正在排隊的任務(wù)。如果任務(wù)在等待鎖,可中斷鎖允許任務(wù)立即響應取消請求,釋放線程資源,提升系統(tǒng)響應速度。
- 靈活的超時控制:
雖然 tryLock(timeout) 也可以避免無限等待,但 lockInterruptibly() 提供了更靈活的被動響應機制(由外部監(jiān)控線程決定何時停止,而不是固定時間)。
5. 最佳實踐與注意事項
在使用 lockInterruptibly() 時,必須遵循嚴格的編碼規(guī)范,否則可能導致死鎖或異常。
5.1 正確的解鎖姿勢
如果 lockInterruptibly() 拋出了 InterruptedException,說明鎖沒有獲取成功。此時不能調(diào)用 unlock(),否則會拋出 IllegalMonitorStateException。
推薦的標準寫法:
boolean locked = false;
try {
lock.lockInterruptibly();
locked = true; // 標記鎖獲取成功
// --- 業(yè)務(wù)邏輯 ---
} catch (InterruptedException e) {
// 處理中斷
Thread.currentThread().interrupt(); // 恢復中斷狀態(tài),不要吞掉中斷
} finally {
if (locked) {
lock.unlock(); // 只有獲取成功才解鎖
}
}5.2 不要吞掉中斷信號
在 catch (InterruptedException e) 塊中,除非你打算立即結(jié)束線程,否則最好恢復中斷狀態(tài):Thread.currentThread().interrupt();
這樣上層調(diào)用者才能知道線程被中斷過,以便做進一步處理。
6. 總結(jié)
- 鎖可中斷 是
ReentrantLock提供的高級特性,通過lockInterruptibly()實現(xiàn)。 - 它允許線程在等待鎖的過程中響應中斷信號,避免無限期阻塞。
- 它不會強制釋放已經(jīng)持有的鎖,以保證數(shù)據(jù)一致性。
- 使用時需注意
try-finally的正確寫法,避免在未獲取鎖時調(diào)用unlock()。
掌握“鎖可中斷”機制,能讓你在面對復雜的并發(fā)場景(如死鎖恢復、任務(wù)取消)時,擁有更多的控制權(quán)和靈活性,是編寫高健壯性 Java 并發(fā)程序的必備技能。
到此這篇關(guān)于Java 并發(fā)編程之深入理解“鎖可中斷”機制的文章就介紹到這了,更多相關(guān)java鎖可中斷內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Druid基本配置及內(nèi)置監(jiān)控使用_動力節(jié)點Java學院整理
這篇文章主要介紹了Druid基本配置及內(nèi)置監(jiān)控使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
JAVA數(shù)據(jù)寫入生成excel文件和發(fā)送郵件
這篇文章主要介紹了JAVA數(shù)據(jù)寫入生成excel文件和發(fā)送郵件,流程:先導包 => 郵箱開啟配置 => java寫好配置類 => 測試發(fā)送 => 數(shù)據(jù)寫入excel => 郵件帶附件發(fā)送2024-06-06
Java基礎(chǔ) Servlet監(jiān)聽器詳解
這篇文章主要介紹了Java基礎(chǔ) Servlet監(jiān)聽器詳解的相關(guān)資料,需要的朋友可以參考下2017-07-07
spring中FactoryBean中的getObject()方法實例解析
這篇文章主要介紹了spring中FactoryBean中的getObject()方法實例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-02-02
關(guān)于Spring的統(tǒng)一功能處理(攔截器)實現(xiàn)
這篇文章主要介紹了關(guān)于Spring的統(tǒng)一功能處理(攔截器)實現(xiàn),每個方法中都要單獨寫用戶登錄驗證的方法,即使封裝成公共方法,也一樣要傳參調(diào)用和在方法中進行判斷,需要的朋友可以參考下2023-05-05
MyBatis自定義映射關(guān)系和關(guān)聯(lián)查詢實現(xiàn)方法詳解
這篇文章主要介紹了MyBatis自定義映射關(guān)系和關(guān)聯(lián)查詢實現(xiàn)方法,當POJO屬性名與數(shù)據(jù)庫列名不一致時,需要自定義實體類和結(jié)果集的映射關(guān)系,在MyBatis注解開發(fā)中,使用@Results定義并使用自定義映射,使用 @ResultMap使用自定義映射2023-04-04

