场景:
1、ExecutorService执行器中的 execute() 和 submit() 都是执行异步线程任务,但 execute() 没有返回值,而 submit() 允许有返回值
2、CompletableFuture 的 runAsync() 没有返回值、supplyAsync() 有返回值
一、ExecutorService 线程块内异常:
1、execute() 执行过程中出现异常,会把异常信息打印到控制台,程序终止执行
测试代码:
System.out.println("测试execute()内部异常");
executorService.execute(() -> {
int i = 5 / 0;
System.out.println("executorService.submit()执行了");
});
输出:
主线程执行开始
测试execute()内部异常
Exception in thread "pool-2-thread-5" java.lang.ArithmeticException: / by zero
at com.hkl.mpjoin.modules.testOne.TestOneController.lambda$testExecutors$1(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
线程池任务执行了
2、submit() 执行过程中出现异常,不会把异常信息打印到控制台,程序会终止执行
测试代码:
System.out.println("测试submit()内部异常");
executorService.submit(() -> {
int i = 5 / 0;
System.out.println("executorService.submit()执行了");
return "a";
});
输出:
主线程执行开始
测试submit()内部异常
线程池任务执行了
小结:
1、在都不捕捉异常的情况下,execute() 会输出异常信息并终止程序,submit() 不会输出异常信息、但会终止程序
2、如果不需要返回值的情况下,建议使用 execute() 执行异步线程,因为即使不捕捉也会输出异常信息到控制台或日志文件,便于排查问题
3、如果需要返回值,使用 submit() 方法块需要用 try/catch 捕捉异常信息手动打印 e.printStackTrace() 并输出到日志文件、并把捕捉的异常抛出去,否则无法查看异常信息,不便于排查问题
4、ScheduledExecutorService 日程线程池中 schedule()、execute() 和 submit() 都不会输出异常信息、但会终止程序,需要用 try/catch 捕捉异常打印出来 e.printStackTrace() (把捕捉的异常抛出去)
5、线程池、CompletableFuture 之类的都属于异步线程,如果异步内出现异常只打断当前异步线程运行,不会打断主线程运行
6、手动 try catch 打印、记录日志、抛出异常等处理示例:
executorService.submit(() -> {
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
log.error("executorService.submit 出现异常:" + e.getMessage());
//ThrowUtil.fail("异常");
throw e;
}
System.out.println("executorService.submit 执行了");
});
二、CompletableFuture 线程块内异常:
1、CompletableFuture 的 runAsync()、supplyAsync() 等异步线程出现异常也不会打印输出异常、但会终止当前线程运行
2、需要手动 try catch 打印、记录日志、抛出异常等处理
CompletableFuture.runAsync(() -> {
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
log.error("CompletableFuture.runAsync 出现异常:" + e.getMessage());
//ThrowUtil.fail("异常");
throw e;
}
System.out.println("CompletableFuture.runAsync 运行了");
});
CompletableFuture.supplyAsync(() -> {
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
log.error("CompletableFuture.supplyAsync 出现异常:" + e.getMessage());
//ThrowUtil.fail("异常");
throw e;
}
System.out.println("CompletableFuture.supplyAsync 运行了");
return null;
});
3、如果异步内出现异常只打断当前异步线程运行,不会打断主线程运行
4、包括延迟执行器的异步线程也是如此
CompletableFuture.runAsync(() -> {
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
log.error("CompletableFuture.runAsync 延迟2秒 出现异常:" + e.getMessage());
throw e;
//ThrowUtil.fail("异常");
}
System.out.println("CompletableFuture.runAsync 延迟2秒 运行了");
}, CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, executorService));
小结:
1、线程池任务、异步线程任务中出现运行时异常,会打断当前线程运行并且看不见异常信息,但不会打断主线程
2、线程池任务、异步线程任务中,推荐使用 try catch 捕捉异常,log.error("处理数据异常:", e) 打印出异常信息
3、如果需要打断异步程序,只需在 try catch 块中 throw e; 或 ThrowUtil.fail("异常"); 抛出系统异常或业务异常即可