Java并發(fā)編程中高頻異常的原因,場景與解決方案全解析
在 Java 并發(fā)編程中,異常處理是保障程序穩(wěn)定性的核心環(huán)節(jié)。新手開發(fā)者常常會被InterruptedException、IllegalMonitorStateException等并發(fā)相關(guān)異常困擾,這些異常不僅定位困難,還可能導(dǎo)致程序死鎖、數(shù)據(jù)錯亂甚至服務(wù)崩潰。本文將深度解析 4 類高頻并發(fā)異常的產(chǎn)生原因、典型場景,并給出可落地的解決方案,幫你徹底避開這些 “坑”。
一、java.lang.InterruptedException(中斷異常)
1. 異常本質(zhì)
InterruptedException是受檢異常,表示線程在執(zhí)行阻塞操作(如sleep()、wait())時被其他線程中斷,導(dǎo)致阻塞狀態(tài)被強制打斷。
2. 觸發(fā)場景
當(dāng)線程調(diào)用Thread.sleep(long)、Object.wait()、Thread.join()等阻塞方法時,其他線程調(diào)用該線程的interrupt()方法,就會觸發(fā)此異常。
3. 錯誤示例
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
// 線程休眠10秒
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("線程休眠被中斷:" + e.getMessage());
// 重置中斷狀態(tài)(關(guān)鍵!)
Thread.currentThread().interrupt();
}
});
t1.start();
// 1秒后中斷t1線程
Thread.sleep(1000);
t1.interrupt();
}
}4. 解決方案
- 捕獲異常后重置中斷狀態(tài):異常會清除線程的中斷標(biāo)記,需調(diào)用
Thread.currentThread().interrupt()恢復(fù),避免后續(xù)邏輯無法感知中斷。 - 優(yōu)雅處理中斷:不要吞掉異常,根據(jù)業(yè)務(wù)場景決定是否終止線程(如任務(wù)取消時)。
二、java.lang.IllegalMonitorStateException(非法監(jiān)視器狀態(tài)異常)
1. 異常本質(zhì)
調(diào)用wait()、notify()、notifyAll()時,當(dāng)前線程未持有該對象的監(jiān)視器鎖(即未進入synchronized塊 / 方法),或鎖對象與調(diào)用方法的對象不一致。
2. 觸發(fā)場景
- 場景 1:未在
synchronized塊內(nèi)調(diào)用wait()/notify(); - 場景 2:
synchronized鎖定的對象 ≠ 調(diào)用wait()的對象; - 場景 3:混用
Lock(Condition.await()/signal())和synchronized(wait()/notify())。
3. 錯誤示例 vs 正確示例
錯誤示例(未加 synchronized)
public class IllegalMonitorDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// 直接調(diào)用wait(),未持有l(wèi)ock的鎖
lock.wait(); // 拋出IllegalMonitorStateException
}
}
正確示例
public class IllegalMonitorDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
synchronized (lock) { // 必須先獲取lock的監(jiān)視器鎖
lock.wait(); // 正確:當(dāng)前線程持有l(wèi)ock的鎖
}
}
}
4. 解決方案
核心原則:調(diào)用wait()/notify()的代碼必須在synchronized塊 / 方法內(nèi),且鎖定的對象必須是調(diào)用這些方法的對象;
Lock+Condition 用法:若使用ReentrantLock,需通過Condition.await()/signal()替代wait()/notify(),且調(diào)用前需獲取Lock鎖:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock(); // 獲取鎖
try {
condition.await(); // 替代wait()
} finally {
lock.unlock(); // 釋放鎖
}
三、java.util.ConcurrentModificationException(并發(fā)修改異常)
1. 異常本質(zhì)
單線程下迭代集合時修改集合(如 add/remove),或多線程同時修改非線程安全集合(如ArrayList),導(dǎo)致迭代器檢測到集合結(jié)構(gòu)被意外修改。
2. 觸發(fā)場景
ArrayList、HashMap等集合的方法(add()、remove())未加鎖,多線程并發(fā)修改時,迭代器的modCount(修改次數(shù))與expectedModCount不一致,觸發(fā)異常。
3. 錯誤示例(多線程修改 ArrayList)
public class ConcurrentModificationDemo {
private static final List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
// 線程1:循環(huán)添加元素
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
}).start();
// 線程2:循環(huán)迭代并修改
new Thread(() -> {
for (Integer num : list) { // 迭代時觸發(fā)檢查
list.remove(num); // 拋出ConcurrentModificationException
}
}).start();
}
}
4. 解決方案
- 單線程場景:迭代時修改集合需使用迭代器的
remove()方法,而非集合的remove(); - 多線程場景:
- 方案 1:使用線程安全集合(如
CopyOnWriteArrayList、ConcurrentHashMap); - 方案 2:對非線程安全集合加鎖(如
synchronized或ReentrantLock); - 方案 3:使用
Collections.synchronizedList(new ArrayList<>())包裝集合(注意:迭代時仍需手動加鎖)。
- 方案 1:使用線程安全集合(如
正確示例(CopyOnWriteArrayList)
// 線程安全的ArrayList替代方案 private static final List<Integer> list = new CopyOnWriteArrayList<>();
四、java.util.concurrent.RejectedExecutionException(拒絕執(zhí)行異常)
1. 異常本質(zhì)
向線程池提交任務(wù)時,線程池已達到最大處理能力(核心線程 + 非核心線程 + 任務(wù)隊列均滿),且拒絕策略為AbortPolicy(默認(rèn)),導(dǎo)致任務(wù)被拒絕執(zhí)行。
2. 觸發(fā)場景
線程池參數(shù)配置不合理,提交的任務(wù)數(shù)超過其最大容量:
- 核心線程數(shù):
corePoolSize; - 最大線程數(shù):
maximumPoolSize; - 任務(wù)隊列容量:
workQueue.size(); - 最大可處理任務(wù)數(shù) =
maximumPoolSize+workQueue.size()。
3. 錯誤示例(任務(wù)數(shù)超過線程池容量)
public class RejectedExecutionDemo {
public static void main(String[] args) {
// 配置線程池:核心2,最大5,隊列10,拒絕策略AbortPolicy
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.AbortPolicy() // 默認(rèn)拒絕策略:直接拋異常
);
// 提交16個任務(wù)(最大容量5+10=15),第16個被拒絕
for (int i = 0; i < 16; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(1000);
System.out.println("執(zhí)行任務(wù):" + finalI);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}
4. 解決方案
- 合理配置線程池參數(shù):根據(jù)業(yè)務(wù)場景調(diào)整
corePoolSize、maximumPoolSize、隊列容量,避免任務(wù)堆積; - 選擇合適的拒絕策略:
AbortPolicy:默認(rèn),拋異常(適合核心任務(wù),需感知任務(wù)拒絕);CallerRunsPolicy:由提交任務(wù)的線程執(zhí)行(降級處理,避免任務(wù)丟失);DiscardPolicy:靜默丟棄任務(wù)(非核心任務(wù));DiscardOldestPolicy:丟棄隊列最老的任務(wù),嘗試提交新任務(wù);
- 異步降級:結(jié)合消息隊列(如 RabbitMQ)削峰填谷,避免瞬時任務(wù)量超過線程池承載能力。
優(yōu)化示例(使用 CallerRunsPolicy)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.CallerRunsPolicy() // 提交線程執(zhí)行被拒絕的任務(wù)
);
總結(jié)
本文梳理了 Java 并發(fā)編程中 4 類高頻異常的核心要點,關(guān)鍵總結(jié)如下:
- InterruptedException:阻塞方法被中斷時拋出,捕獲后需重置中斷狀態(tài),避免吞掉異常;
- IllegalMonitorStateException:調(diào)用 wait/notify 前必須持有對象的監(jiān)視器鎖,Lock 需搭配 Condition 使用;
- ConcurrentModificationException:非線程安全集合并發(fā)修改觸發(fā),優(yōu)先使用 CopyOnWriteArrayList/ConcurrentHashMap;
- RejectedExecutionException:線程池任務(wù)超限觸發(fā),需合理配置參數(shù) + 選擇合適的拒絕策略。
以上就是Java并發(fā)編程中高頻異常的原因,場景與解決方案全解析的詳細(xì)內(nèi)容,更多關(guān)于Java并發(fā)編程高頻異常的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IntelliJ IDEA中使用mybatis-generator的示例
這篇文章主要介紹了IntelliJ IDEA中使用mybatis-generator,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04
SpringBoot讀取Resource目錄文件的五種常見方式
在Spring Boot開發(fā)中,我們經(jīng)常需要讀取src/main/resources目錄下的文件,src/main/resources 目錄下通常存放配置文件、模板、靜態(tài)資源、SQL腳本等,本文給大家介紹了SpringBoot讀取Resource目錄文件的五種常見方式,需要的朋友可以參考下2025-07-07
SpringCloud與Consul集成實現(xiàn)負(fù)載均衡功能
負(fù)載均衡基本概念有:實服務(wù)、實服務(wù)組、虛服務(wù)、調(diào)度算法、持續(xù)性等,其常用應(yīng)用場景主要是服務(wù)器負(fù)載均衡,鏈路負(fù)載均衡。這篇文章主要介紹了SpringCloud與Consul集成實現(xiàn)負(fù)載均衡 ,需要的朋友可以參考下2018-09-09
spring boot tomcat jdbc pool的屬性綁定
這篇文章主要介紹了spring boot tomcat jdbc pool的屬性綁定的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友參考下2018-01-01
java前后端傳值,參數(shù)有集合類型的數(shù)據(jù)時的兩種操作方式
這篇文章主要介紹了java前后端傳值,參數(shù)有集合類型的數(shù)據(jù)時的兩種操作方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
JAVA中調(diào)用C語言函數(shù)的實現(xiàn)方式
這篇文章主要介紹了JAVA中調(diào)用C語言函數(shù)的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
Mac?Maven環(huán)境搭建安裝和配置超詳細(xì)步驟
這篇文章主要給大家介紹了關(guān)于Mac?Maven環(huán)境搭建安裝和配置的超詳細(xì)步驟,Maven是一種常用的Java構(gòu)建工具,它可以自動化構(gòu)建、測試和打包Java項目,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10

