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. 总结
线程池是一个非常有用的工具,通过它我们可以更好地管理和控制线程的执行。但是在使用线程池时,可能会遇到一些问题,比如任务执行异常等,并且可能会导致线程池抛出异常。
我们可以通过增加队列长度、调整核心线程数和最大线程数、使用不同的拒绝策略等方式来解决线程池异常,并确保线程池的稳定和高效运行。