解决Java线程池任务执行异常「ThreadPoolTaskExecutionException」的解决方案

1. 线程池介绍

线程池是用于管理和控制线程执行的一种机制,通过线程池可以实现线程的重用,减少系统资源的消耗。

在 Java 中,线程池主要由 ThreadPoolExecutor 类来实现,该类是 ExecutorService 接口的实现类。

ThreadPoolExecutor 中的重要参数包括:

corePoolSize:核心线程数

maximumPoolSize:最大线程数

workQueue:等待队列,用来存放等待执行的任务

keepAliveTime:非核心线程闲置超时时间

threadFactory:线程工厂,用来创建线程

handler:拒绝策略,当队列和线程池都满了之后,如何处理新的任务

2. 线程池任务执行异常

在使用线程池执行任务时,可能会遇到线程执行异常,其中一种可能的异常是 ThreadPoolTaskExecutionException。

2.1 异常原因

ThreadPoolTaskExecutionException 异常的原因通常包括以下几点:

任务执行异常,比如 NPE(NullPointerException)

任务被取消

线程池关闭或者繁忙

2.2 异常示例

下面是一个简单的示例,使用线程池执行任务,并打印任务执行结果:

ExecutorService executor = Executors.newFixedThreadPool(5);

for (int i = 0; i < 10; i++) {

executor.submit(() -> {

System.out.println(Thread.currentThread().getName() + " start");

if (Math.random() > 0.5) {

throw new RuntimeException("task exception");

}

System.out.println(Thread.currentThread().getName() + " end");

});

}

executor.shutdown();

上述代码中,我们创建一个固定大小为 5 的线程池,并提交了 10 个任务,任务会执行一些随机操作,有可能抛出 RuntimeException。

在这个示例中,运行可能会产生 ThreadPoolTaskExecutionException 异常:

java.util.concurrent.ExecutionException: org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@7e4219f9[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]] did not accept task: java.util.concurrent.FutureTask@2ff4f00c

at java.util.concurrent.FutureTask.report(FutureTask.java:122)

at java.util.concurrent.FutureTask.get(FutureTask.java:192)

at com.example.demo.TestThreadPoolTaskExecutionException.main(TestThreadPoolTaskExecutionException.java:18)

Caused by: org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@7e4219f9[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]] did not accept task: java.util.concurrent.FutureTask@2ff4f00c

at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:310)

at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:289)

at com.example.demo.TestThreadPoolTaskExecutionException.lambda$main$0(TestThreadPoolTaskExecutionException.java:14)

at java.util.concurrent.FutureTask.run(FutureTask.java:266)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at java.lang.Thread.run(Thread.java:748)

我们可以看到,异常链中最终抛出了一个 TaskRejectedException,这是因为线程池的队列和线程都已满,无法接受新的任务。

3. 解决方案

遇到这种线程池异常,我们可以通过以下几种方式来解决:

3.1 增加队列长度

通过调整线程池的等待队列长度,可以让线程池接受更多的任务。

例如,我们可以将等待队列长度设置成 LinkedBlockingQueue 的默认值,即 Integer.MAX_VALUE,以免任务被拒绝。

ExecutorService executor = new ThreadPoolExecutor(

5, 10, 60L, TimeUnit.SECONDS,

new LinkedBlockingQueue<>(),

Executors.defaultThreadFactory(),

new ThreadPoolExecutor.CallerRunsPolicy());

for (int i = 0; i < 100; i++) {

executor.submit(() -> {

System.out.println(Thread.currentThread().getName() + " start");

if (Math.random() > temperature) {

throw new RuntimeException("task exception");

}

System.out.println(Thread.currentThread().getName() + " end");

});

}

executor.shutdown();

在上述示例中,我们创建了一个拥有 5-10 个线程的线程池,并将等待队列设置成了默认的 LinkedBlockingQueue,可以接受非常大的任务数。

3.2 调整核心线程数与最大线程数

通过调整线程池的核心线程数和最大线程数,可以更好地适应不同的任务需求。

核心线程数是线程池中保持活跃的线程数,最大线程数是包括核心线程在内的最大线程数。

例如,如果我们预计的并发数比较高,可以把核心线程数和最大线程数都调大一些:

ExecutorService executor = new ThreadPoolExecutor(

10, 20, 60L, TimeUnit.SECONDS,

new ArrayBlockingQueue<>(100),

Executors.defaultThreadFactory(),

new ThreadPoolExecutor.CallerRunsPolicy());

for (int i = 0; i < 100; i++) {

executor.submit(() -> {

System.out.println(Thread.currentThread().getName() + " start");

if (Math.random() > 0.6) {

throw new RuntimeException("task exception");

}

System.out.println(Thread.currentThread().getName() + " end");

});

}

executor.shutdown();

在上述示例中,我们创建了一个拥有 10-20 个线程的线程池,并且将等待队列设置为长度为 100 的 ArrayBlockingQueue。

3.3 使用不同的拒绝策略

如果线程池的队列和线程都已满,那么新的任务将被拒绝。此时可以配置不同的拒绝策略,来更好地处理新的任务。

例如,我们可以使用 ThreadPoolExecutor 的 CallerRunsPolicy 策略,让主线程调用被拒绝的任务,从而避免任务丢失。

ExecutorService executor = new ThreadPoolExecutor(

5, 10, 60L, TimeUnit.SECONDS,

new ArrayBlockingQueue<>(5),

Executors.defaultThreadFactory(),

new ThreadPoolExecutor.CallerRunsPolicy());

for (int i = 0; i < 20; i++) {

executor.submit(() -> {

System.out.println(Thread.currentThread().getName() + " start");

if (Math.random() > 0.6) {

throw new RuntimeException("task exception");

}

System.out.println(Thread.currentThread().getName() + " end");

});

}

executor.shutdown();

在上述示例中,我们创建了一个拥有 5-10 个线程的线程池,并且将等待队列设置为长度为 5 的 ArrayBlockingQueue,同时使用了 CallerRunsPolicy 策略。

4. 总结

线程池是一个非常有用的工具,通过它我们可以更好地管理和控制线程的执行。但是在使用线程池时,可能会遇到一些问题,比如任务执行异常等,并且可能会导致线程池抛出异常。

我们可以通过增加队列长度、调整核心线程数和最大线程数、使用不同的拒绝策略等方式来解决线程池异常,并确保线程池的稳定和高效运行。

免责声明:本文来自互联网,本站所有信息(包括但不限于文字、视频、音频、数据及图表),不保证该信息的准确性、真实性、完整性、有效性、及时性、原创性等,版权归属于原作者,如无意侵犯媒体或个人知识产权,请来电或致函告之,本站将在第一时间处理。猿码集站发布此文目的在于促进信息交流,此文观点与本站立场无关,不承担任何责任。

后端开发标签