线程池中的一个线程异常了会发生什么?
参考文档
https://zhuanlan.zhihu.com/p/136571068
- 线程抛出异常信息
- 异常线程被线程池回收;线程不是被回收而是线程池把这个线程移除掉,同时创建一个新的线程放到线程池中。
- 其他非异常线程不受影响,继续执行
多线程(线程池)异常捕获
- 线程池中的一个线程异常了会被怎么处理?:https://zhuanlan.zhihu.com/p/136571068
- 线程池的拒绝策略、异常捕获:https://blog.csdn.net/qq_43783527/article/details/125044548
- Java多线程:捕获线程异常:https://blog.csdn.net/lilizhou2008/article/details/106953380
- (九)线程池异常捕获:https://blog.csdn.net/yudianxiaoxiao/article/details/107580289
Executors 线程池
线程池有两种提交线程的方式,execute() 和 submit() ,使用时有以下区别:
- execute 没有返回值。可以执行任务,但无法判断任务是否成功完成。
- 实现 Runnable 接口
- submit 返回一个 future ,可以用这个 future 来判断任务是否成功完成。
- 实现 Callable 接口
public static void main(String[] args) {
ThreadPoolTaskExecutor executorService = buildThreadPoolTaskExecutor();
executorService.execute(() -> run("execute方法"));
executorService.submit(() -> run("submit方法"));
}
private static void run(String name) {
String printStr = "【thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name+"】";
System.out.println(printStr);
throw new RuntimeException(printStr + ",出现异常");
}
private static ThreadPoolTaskExecutor buildThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
executorService.setThreadNamePrefix("(小罗技术笔记)-");
executorService.setCorePoolSize(5);
executorService.setMaxPoolSize(10);
executorService.setQueueCapacity(100);
executorService.setKeepAliveSeconds(10);
executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executorService.initialize();
return executorService;
}
- 运行程序, execute 执行方式抛出异常显示在控制台了;submit 执行程序并没有任何异常打印
这是因为 submit() 方法执行线程后会返回线程执行结果,而相应的执行异常信息也被放入到结果中了,可以通过结果对象来获取异常信息
Future<?> result=executorService.submit(() -> run("submit方法"));
try {
result.get();
}catch (Exception e){
e.printStackTrace();
}
- 如果线程抛出异常,则使用 get() 获取结果时会抛出异常信息
捕获线程池线程异常
参考:https://www.cnblogs.com/549294286/p/4618798.html
Java 中线程执行的任务接口 java.lang.Runnable 要求不抛出 Checked 异常,但是当实现的 run() 方法中出现了异常,会怎么样呢?又怎么啦捕获异常信息呢?
通常 java.lang.Thread 对象运行设置一个默认的异常处理方法,这个默认的静态全局的异常捕获方法是直接输出异常堆栈。
java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
我们可以覆盖此默认实现,只需要一个自定义的 java.lang.Thread.UncaughtExceptionHandler 接口实现即可。
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
线程池中却比较特殊,默认情况下,线程池 java.util.concurrent.ThreadPoolExecutor 会 Catch 住所有异常, 当任务执行完成 (java.util.concurrent.ExecutorService.submit(Callable)) 获取其结果时 Future.get()) 会抛出此异常信息 RuntimeException 。
V get() throws InterruptedException, ExecutionException;
其中 ExecutionException 异常即是java.lang.Runnable 或者 java.util.concurrent.Callable 抛出的异常。
java.util.concurrent.ThreadPoolExecutor 中预留了一个方法,在任务执行完毕进行扩展(当然也预留一个protected 方法在任务执行前扩展, beforeExecute(Thread t, Runnable r))。
protected void afterExecute(Runnable r, Throwable t) { }
此方法的默认实现为空,这样我们就可以通过继承或者覆盖ThreadPoolExecutor 来达到自定义的错误处理。
重写线程池 afterExecute() 方法实现异常捕获
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, //
new ArrayBlockingQueue<Runnable>(10000),//
new DefaultThreadFactory()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
printException(r, t);
}
};
private static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone())
future.get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
log.error(t.getMessage(), t);
}
- 如果使用 @Configuration + @Bean(”threadPoolTaskExecutor“) 可以配置系统线程池异常捕获
事实上 afterExecute 并不会总是抛出异常 Throwable t,通过查看源码得知,异常是封装在此时的Future对象中的, 而此 Future 对象其实是一个 java.util.concurrent.FutureTask 的实现,默认的 run 方法其实调用的 java.util.concurrent.FutureTask.Sync.innerRun()。
吃掉了异常,将异常存储在java.util.concurrent.FutureTask.Sync的exception字段中
当我们获取异步执行的结果时, java.util.concurrent.FutureTask.get()
异常就会被包装成ExecutionException异常抛出。
ScheduledThreadPoolExecutor
java.util.concurrent.ScheduledThreadPoolExecutor 是继承 ThreadPoolExecutor 的,因此情况类似。
主线程和子线程
如果主线程想拿到子线程的异常,比如展示给界面,该怎么做?
- 友好的做法是子线程不抛出异常,返回不同的结果,或者将异常封装到return对象中。父对象根据此结果/异常封装友好的提示给界面。