Java线程池ExecutorService、日程线程池、CompletableFuture、延迟执行器的 execute()、submit()、runAsync() 等异步线程出现异常分析

场景:
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("异常"); 抛出系统异常或业务异常即可

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,可以使用配置类来配置Executor线程池。其中,常用的实现类是`ExecutorService`和`ThreadPoolTaskExecutor`。 首先,需要创建一个配置类,可以命名为`ThreadPoolConfig`或者其他你喜欢的名称。在这个类中,你需要使用`@Configuration`注解来标识它是一个配置类,并且使用`@EnableAsync`注解来启用异步执行。 接下来,你需要定义一个`ExecutorService`或`ThreadPoolTaskExecutor` Bean,并使用`@Bean`注解将其标识为一个Bean。你可以根据项目的需求来选择使用哪个实现类。 如果选择使用`ExecutorService`,可以按照以下方式配置: ```java @Configuration @EnableAsync public class ThreadPoolConfig { @Bean public ExecutorService executorService() { return Executors.newFixedThreadPool(10); // 配置线程池的大小 } } ``` 如果选择使用`ThreadPoolTaskExecutor`,可以按照以下方式配置: ```java @Configuration @EnableAsync public class ThreadPoolConfig { @Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); // 设置核心线程数 executor.setMaxPoolSize(20); // 设置最大线程数 executor.setQueueCapacity(30); // 设置队列容量 executor.setThreadNamePrefix("my-executor-"); // 设置线程名称前缀 executor.initialize(); // 初始化 return executor; } } ``` 在上述配置中,你可以根据实际需求来设置线程池的大小、队列容量等参数。通过这种方式,你就可以在应用程序中注入`ExecutorService`或`ThreadPoolTaskExecutor`,并使用它来执行异步任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值