Java并发编程中高频异常的原因,场景与解决方案全解析

 更新时间:2026年03月09日 09:27:07   作者:Akk_it  
在Java并发编程中,异常处理是保障程序稳定性的核心环节,本文将深度解析4类高频并发异常的产生原因、典型场景,并给出可落地的解决方案,帮你彻底避开这些坑

在 Java 并发编程中,异常处理是保障程序稳定性的核心环节。新手开发者常常会被InterruptedExceptionIllegalMonitorStateException等并发相关异常困扰,这些异常不仅定位困难,还可能导致程序死锁、数据错乱甚至服务崩溃。本文将深度解析 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:混用LockCondition.await()/signal())和synchronizedwait()/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. 触发场景

ArrayListHashMap等集合的方法(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:使用线程安全集合(如CopyOnWriteArrayListConcurrentHashMap);
    • 方案 2:对非线程安全集合加锁(如synchronizedReentrantLock);
    • 方案 3:使用Collections.synchronizedList(new ArrayList<>())包装集合(注意:迭代时仍需手动加锁)。

正确示例(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. 解决方案

  • 合理配置线程池参数:根据业务场景调整corePoolSizemaximumPoolSize、队列容量,避免任务堆积;
  • 选择合适的拒绝策略
    • 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中对list元素进行排序的方法详解

    Java中对list元素进行排序的方法详解

    这篇文章主要介绍了Java中对list元素进行排序的方法详解,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • 新手了解java基础知识(一)

    新手了解java基础知识(一)

    这篇文章主要介绍了Java基础知识,本文介绍了Java语言相关的基础知识、历史介绍、主要应用方向等内容,需要的朋友可以参考下,希望对你有所帮助
    2021-07-07
  • Java报NoClassDefFoundError异常的原因及解决

    Java报NoClassDefFoundError异常的原因及解决

    在 Java 开发过程中, java.lang.NoClassDefFoundError 是一个令人头疼的运行时错误,本文将深入探讨这一问题的原因和常见场景,并提供实用的解决方法,希望对大家有所帮助
    2025-03-03
  • Java实现threadLocal线程池获取

    Java实现threadLocal线程池获取

    本文主要介绍了Java实现threadLocal线程池获取,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • JAVA如何调用Shell脚本

    JAVA如何调用Shell脚本

    本篇文章主要介绍了JAVA如何调用Shell脚本,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • SpringBoot如何读取application.properties配置文件

    SpringBoot如何读取application.properties配置文件

    这篇文章主要介绍了SpringBoot如何读取application.properties配置文件问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • 从零开始使用IDEA创建SpringBoot项目(图文)

    从零开始使用IDEA创建SpringBoot项目(图文)

    这篇文章主要介绍了从零开始使用IDEA创建SpringBoot项目(图文),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • 解决JAVA遍历List集合,删除数据时出现的问题

    解决JAVA遍历List集合,删除数据时出现的问题

    这篇文章主要介绍了解决JAVA遍历List集合时,删除数据出现的问题,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • java大文件上传处理方法实例代码

    java大文件上传处理方法实例代码

    在Java中实现大文件上传功能,确实需要考虑到文件大小可能超出内存限制、网络传输稳定性等因素,这篇文章主要介绍了java大文件上传处理方法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-09-09
  • springboot全局异常处理代码实例

    springboot全局异常处理代码实例

    这篇文章主要介绍了springboot全局异常处理代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01

最新评论