在 java 并发编程中,异常处理是保障程序稳定性的核心环节。新手开发者常常会被interruptedexception、illegalmonitorstateexception等并发相关异常困扰,这些异常不仅定位困难,还可能导致程序死锁、数据错乱甚至服务崩溃。本文将深度解析 4 类高频并发异常的产生原因、典型场景,并给出可落地的解决方案,帮你彻底避开这些 “坑”。
一、java.lang.interruptedexception(中断异常)
1. 异常本质
interruptedexception是受检异常,表示线程在执行阻塞操作(如sleep()、wait())时被其他线程中断,导致阻塞状态被强制打断。
2. 触发场景
当线程调用thread.sleep(long)、object.wait()、thread.join()等阻塞方法时,其他线程调用该线程的interrupt()方法,就会触发此异常。
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());
// 重置中断状态(关键!)
thread.currentthread().interrupt();
}
});
t1.start();
// 1秒后中断t1线程
thread.sleep(1000);
t1.interrupt();
}
}4. 解决方案
- 捕获异常后重置中断状态:异常会清除线程的中断标记,需调用
thread.currentthread().interrupt()恢复,避免后续逻辑无法感知中断。 - 优雅处理中断:不要吞掉异常,根据业务场景决定是否终止线程(如任务取消时)。
二、java.lang.illegalmonitorstateexception(非法监视器状态异常)
1. 异常本质
调用wait()、notify()、notifyall()时,当前线程未持有该对象的监视器锁(即未进入synchronized块 / 方法),或锁对象与调用方法的对象不一致。
2. 触发场景
- 场景 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(),未持有lock的锁
lock.wait(); // 抛出illegalmonitorstateexception
}
}
正确示例
public class illegalmonitordemo {
private static final object lock = new object();
public static void main(string[] args) throws interruptedexception {
synchronized (lock) { // 必须先获取lock的监视器锁
lock.wait(); // 正确:当前线程持有lock的锁
}
}
}
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(并发修改异常)
1. 异常本质
单线程下迭代集合时修改集合(如 add/remove),或多线程同时修改非线程安全集合(如arraylist),导致迭代器检测到集合结构被意外修改。
2. 触发场景
arraylist、hashmap等集合的方法(add()、remove())未加锁,多线程并发修改时,迭代器的modcount(修改次数)与expectedmodcount不一致,触发异常。
3. 错误示例(多线程修改 arraylist)
public class concurrentmodificationdemo {
private static final list<integer> list = new arraylist<>();
public static void main(string[] args) {
// 线程1:循环添加元素
new thread(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
}).start();
// 线程2:循环迭代并修改
new thread(() -> {
for (integer num : list) { // 迭代时触发检查
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(拒绝执行异常)
1. 异常本质
向线程池提交任务时,线程池已达到最大处理能力(核心线程 + 非核心线程 + 任务队列均满),且拒绝策略为abortpolicy(默认),导致任务被拒绝执行。
2. 触发场景
线程池参数配置不合理,提交的任务数超过其最大容量:
- 核心线程数:
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("执行任务:" + finali);
} catch (interruptedexception e) {
thread.currentthread().interrupt();
}
});
}
executor.shutdown();
}
}
4. 解决方案
- 合理配置线程池参数:根据业务场景调整
corepoolsize、maximumpoolsize、队列容量,避免任务堆积; - 选择合适的拒绝策略:
abortpolicy:默认,抛异常(适合核心任务,需感知任务拒绝);callerrunspolicy:由提交任务的线程执行(降级处理,避免任务丢失);discardpolicy:静默丢弃任务(非核心任务);discardoldestpolicy:丢弃队列最老的任务,尝试提交新任务;
- 异步降级:结合消息队列(如 rabbitmq)削峰填谷,避免瞬时任务量超过线程池承载能力。
优化示例(使用 callerrunspolicy)
threadpoolexecutor executor = new threadpoolexecutor(
2, 5, 60l, timeunit.seconds,
new arrayblockingqueue<>(10),
new threadpoolexecutor.callerrunspolicy() // 提交线程执行被拒绝的任务
);
总结
本文梳理了 java 并发编程中 4 类高频异常的核心要点,关键总结如下:
- interruptedexception:阻塞方法被中断时抛出,捕获后需重置中断状态,避免吞掉异常;
- illegalmonitorstateexception:调用 wait/notify 前必须持有对象的监视器锁,lock 需搭配 condition 使用;
- concurrentmodificationexception:非线程安全集合并发修改触发,优先使用 copyonwritearraylist/concurrenthashmap;
- rejectedexecutionexception:线程池任务超限触发,需合理配置参数 + 选择合适的拒绝策略。
以上就是java并发编程中高频异常的原因,场景与解决方案全解析的详细内容,更多关于java并发编程高频异常的资料请关注代码网其它相关文章!
发表评论