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

