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

