Java并发编程中高频异常的原因,场景与解决方案全解析
在 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并发编程高频异常的资料请关注脚本之家其它相关文章!
相关文章
Java报NoClassDefFoundError异常的原因及解决
在 Java 开发过程中, java.lang.NoClassDefFoundError 是一个令人头疼的运行时错误,本文将深入探讨这一问题的原因和常见场景,并提供实用的解决方法,希望对大家有所帮助2025-03-03
SpringBoot如何读取application.properties配置文件
这篇文章主要介绍了SpringBoot如何读取application.properties配置文件问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-05-05


最新评论