Springboot线程池异常处理
在 Java 多线程编程中,线程池(ThreadPoolExecutor
)是一个常用的工具,用于管理线程的生命周期并提升应用程序的性能。然而,在使用线程池时,异常处理可能会被忽略,从而导致潜在的程序问题甚至崩溃。如果任务出现了异常,会发生什么呢?该怎么处理呢?怎么获取到异常信息来解决异常?想要知道如何解决,就需要了解了解线程池提交任务的两个方法execute
与submit
一.execute
与submit
package demo1;import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class Test2 {public static void main(String[] args) {//只有一个线程的线程池ExecutorService threadPool = Executors.newFixedThreadPool(1);threadPool.execute(()->{System.out.println("Execute: Current thread is " + Thread.currentThread().getName());method_1();});Future<?> submit = threadPool.submit(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());method_1();});Future<?> submit2 = threadPool.submit(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());method_1();});}public static String method_1(){System.out.println("starting.....");int i = 1/0;System.out.println("ending.....");return "ok";}
}
这里我execute
与submit
分别执行两个有异常的任务,同时打印了当前线程,以下是运行结果
可见execute
方法在遇到异常之后会抛出异常,并且线程池中的线程终止
submit
方法遇到异常不会抛出异常
特性 | execute | submit |
---|---|---|
方法定义 | void execute(Runnable command) | Future<?> submit(Runnable task) Future<T> submit(Callable<T> task) |
返回值 | 无返回值,不关心任务的执行结果 | 返回 Future 对象,可用于获取任务结果或状态 |
支持任务类型 | 仅支持 Runnable | 支持 Runnable 和 Callable |
异常处理 | 任务抛出的异常不会被捕获,直接传播到线程池的工作线程 | 任务抛出的异常会被封装在 Future 中,需通过 Future.get() 获取 |
使用场景 | 适用于无需获取任务结果的场景 | 适用于需要获取任务执行结果或捕获异常的场景 |
示例 | threadPool.execute(() -> method_1()); | Future<?> future = threadPool.submit(() -> method_1()); |
任务执行方式 | 直接提交给线程池执行 | 包装为 FutureTask 后交由线程池执行 |
线程池依赖 | 线程池的 execute 方法是基础实现 | submit 方法内部调用 execute 执行任务 |
异常传播位置 | 通过默认的 UncaughtExceptionHandler 处理 | 通过 Future.get() 抛出异常 |
二.如何处理异常
2.1使用try-catch
这里不多赘述,这是最简单明了的方法,直接用try-catch捕获就行
2.2 使用 ThreadPoolExecutor
的 afterExecute
方法
ThreadPoolExecutor
提供了一个 afterExecute
钩子方法,可以在任务完成后检查是否有异常。
通过覆盖此方法,可以捕获所有任务中未被捕获的异常。
package demo1;import java.io.IOException;
import java.util.concurrent.*;public class Test3 {public static void main(String[] args) {ExecutorService threadPool = new /*** @author 方*/ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()) {@Overrideprotected void afterExecute(Runnable r, Throwable t) {super.afterExecute(r, t);if (t != null) {System.out.println("Task threw an exception: " + t.getMessage());}// 针对 Future 的异常处理if (r instanceof Future<?>) {try {((Future<?>) r).get(); // 调用 get 检查任务是否抛出异常} catch (Exception e) {System.out.println("Exception in Future: " + e.getCause());}}}};threadPool.execute(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());throw new RuntimeException("Test1 exception in execute");});threadPool.submit(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());throw new RuntimeException("Test2 exception in submit");});threadPool.submit(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());throw new RuntimeException("Test3 exception in submit");});threadPool.shutdown();}
}
运行结果:
可见execute
方法在遇到异常之后会抛出异常,并且线程池中的线程终止,submit
没有抛出异常
但是他们两个**都记录了异常信息**
2.3设置 UncaughtExceptionHandler
package demo1;import java.io.IOException;
import java.util.concurrent.*;public class Test3 {public static void main(String[] args) {//1.实现一个自己的线程池工厂ThreadFactory factory = (Runnable r) -> {//创建一个线程Thread t = new Thread(r);//给创建的线程设置UncaughtExceptionHandler对象 里面实现异常的默认逻辑t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {//出现异常if (e != null){System.out.println(Thread.currentThread().getName()+e.getMessage());e.printStackTrace();}});return t;};//2.创建一个自己定义的线程池,使用自己定义的线程工厂ExecutorService threadPool = new ThreadPoolExecutor(1,1,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10),factory);threadPool.execute(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());throw new RuntimeException("Test1 exception in execute");});threadPool.submit(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());throw new RuntimeException("Test2 exception in submit");});threadPool.submit(() -> {System.out.println("Execute: Current thread is " + Thread.currentThread().getName());throw new RuntimeException("Test3 exception in submit");});threadPool.shutdown();}
}
运行结果:
可见execute
方法在遇到异常之后会抛出异常,并且线程池中的线程终止,submit
没有抛出异常
三.Springboot中的线程池异常处理
@Bean(MALLCHAT_EXECUTOR)@Primarypublic ThreadPoolTaskExecutor mallchatExecutor() {//spring的线程池ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//线程池优雅停机的关键executor.setWaitForTasksToCompleteOnShutdown(true);executor.setCorePoolSize(10);executor.setMaxPoolSize(10);executor.setQueueCapacity(200);executor.setThreadNamePrefix("mallchat-executor-");//拒绝策略->满了调用线程执行,认为重要任务executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//自己就是一个线程工程executor.setThreadFactory(new MyThreadFactory(executor));executor.initialize();return executor;}
package org.fth.mallchat.common.common.thread;import lombok.AllArgsConstructor;import java.util.concurrent.ThreadFactory;/*** @author 方*/
@AllArgsConstructor
public class MyThreadFactory implements ThreadFactory {private static final MyUncaughtExceptionHandler MyUncaughtExceptionHandler = new MyUncaughtExceptionHandler();private ThreadFactory original;@Overridepublic Thread newThread(Runnable r) {//执行Spring线程自己的创建逻辑Thread thread = original.newThread(r);//我们自己额外的逻辑thread.setUncaughtExceptionHandler(MyUncaughtExceptionHandler);return thread;}
}
package org.fth.mallchat.common.common.thread;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author 方*/
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {private static final Logger log = LoggerFactory.getLogger(MyUncaughtExceptionHandler.class);@Overridepublic void uncaughtException(Thread t, Throwable e) {log.error("Exception in thread",e);}
}
1. 线程池配置 (ThreadPoolTaskExecutor
)
在 Spring Boot 中,ThreadPoolTaskExecutor
用于管理线程池的执行,允许我们设置线程池的核心大小、最大线程数、队列容量等。通过这种配置,我们可以控制线程池的资源使用情况,确保任务的执行效率与可靠性。在你的代码中,线程池的配置包括:
corePoolSize
和maxPoolSize
:指定线程池的最小和最大线程数。这里设置为 10,意味着线程池最多同时运行 10 个线程。queueCapacity
:指定任务队列的容量。任务超过这个容量会被拒绝执行,进入拒绝策略处理。setRejectedExecutionHandler
:设置任务队列满时的拒绝策略。在这个配置中,使用CallerRunsPolicy
,意味着如果队列已满,任务会直接在调用线程中执行,而不是抛出异常。setWaitForTasksToCompleteOnShutdown(true)
:设置线程池在关闭时等待所有任务完成再退出,确保优雅停机。
2. 自定义线程工厂 (ThreadFactory
)
通过 ThreadFactory
,你可以自定义线程的创建过程。在你的代码中,你为每个线程设置了一个异常处理器。这意味着,如果线程内发生未捕获的异常,这些异常会被专门的异常处理器捕获并记录,而不是导致线程崩溃或丢失异常信息。
- 自定义异常处理器:
Thread.setDefaultUncaughtExceptionHandler
会设置线程的默认异常处理器,确保在任何线程中出现未捕获的异常时,异常都能被记录。这个处理器的作用是将异常信息输出到日志中,避免错误被忽略或导致线程不可控。
3. 线程销毁与异常
线程池的行为与异常处理相关:
- 如果线程发生未捕获的异常,
UncaughtExceptionHandler
会记录异常,但不会销毁线程。线程池中的其他线程仍然会继续工作。 - 线程池会自动重用空闲线程。即使某个线程发生异常,线程池仍然会创建新的线程来执行其他任务,只要线程池的资源没有完全用尽。
因此,线程池不会因单个线程的异常而销毁整个线程池,它会继续运行,并且通过异常处理机制记录异常,确保系统的稳定性。
现未捕获的异常时,异常都能被记录。这个处理器的作用是将异常信息输出到日志中,避免错误被忽略或导致线程不可控。
3. 线程销毁与异常
线程池的行为与异常处理相关:
- 如果线程发生未捕获的异常,
UncaughtExceptionHandler
会记录异常,但不会销毁线程。线程池中的其他线程仍然会继续工作。 - 线程池会自动重用空闲线程。即使某个线程发生异常,线程池仍然会创建新的线程来执行其他任务,只要线程池的资源没有完全用尽。
因此,线程池不会因单个线程的异常而销毁整个线程池,它会继续运行,并且通过异常处理机制记录异常,确保系统的稳定性。
不过sumbit
还是必须要get
才能拿到异常信息,我们还是可以通过重写ThreadPoolExecutor
的 afterExecute
方法 不过这样的话就有点麻烦