Springboot利用CompletableFuture异步执行线程(有回调和无回调)
作者:mmseoamin日期:2024-04-29

目录

背景

实现

一、异步线程配置类

 二、自定义异步异常统一处理类

三、实现调用异步(无回调-runAsync())

四、实现调用异步(有回调-supplyAsync())  

五、异步执行错误异常示例

背景

项目中总会有需要异步执行来避免浪费时间资源的情况,这就需要异步操作。异步又分两种:

1、无回调:有一些执行过程对用户而言不需要反馈回调,只需要自己执行即可,且执行过程时间较长(某些第三方接口,如发送短信验证码、查取ip属地等等),如果同步执行,势必会影响到用户体验,这时候就可以使用CompletableFuture.runAsync()方法了。

2、有回调:在执行异步操作结束后,需要获得异步方法返回的值,然后再回调给用户展示,这时候就需要用到CompletableFuture.supplyAsync()方法了。

实现

一、异步线程配置类

/**
 * 异步线程配置类
 */
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(8);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        // 设置队列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置线程名前缀+分组名称
        executor.setThreadNamePrefix("AsyncOperationThread-");
        executor.setThreadGroupName("AsyncOperationGroup");
        // 所有任务结束后关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 初始化
        executor.initialize();
        return executor;
    }
    /**
     * 自定义异步异常
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncException();
    }
}

 二、自定义异步异常统一处理类

/**
 * 异步请求异常错误
 */
public class AsyncException  implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("--------------------异步请求异常捕获---------------------------------");
        System.out.println("Exception message - " + throwable.getMessage());
        System.out.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value - " + param);
        }
        System.out.println("---------------------异步请求异常捕获---------------------------------");
    }
}

三、实现调用异步(无回调-runAsync())

在需要异步的方法前加上@Async再调用即可如下,在service层中新建一个方法,模拟第三方接口请求延时3秒(异步方法和调用方法一定要写在不同的类中 ,如果写在一个类中,是没有效果的!)

@Service
public class LoginLogServiceImpl extends ServiceImpl implements ILoginLogService {
    // 无回调
    @Async
    public CompletableFuture test() {
        return CompletableFuture.runAsync(() -> {
            // 模拟第三方请求加载时间
            Thread.sleep(3000);
            System.out.println("保存成功登录日志");
        });
    }
}

 然后在controller层中调用该方法,

    @PostMapping("/login")
    public Result login() throws InterruptedException {
        System.out.println("登录验证成功");
        // 异步操作
        iLoginLogService.test();
        System.out.println("登录接口请求返回用户成功");
        // 正常返回结果
        return Result.success(200, "登录成功");
    }

执行完成后,我们打开控制台,可以看到, 异步请求在接口执行正常,在接口返回结果后执行完成。

Springboot利用CompletableFuture异步执行线程(有回调和无回调),第1张

四、实现调用异步(有回调-supplyAsync())  

这里我们多采用3个异步线程来模拟实际效果,且延迟时间为1 6 3,看下他们的异步执行输出顺序是否和我们模拟的执行时间相同,如下:

@Service
public class LoginLogServiceImpl extends ServiceImpl implements ILoginLogService {
    // 有返回值的异步方法CompletableFuture.supplyAsync()
    public CompletableFuture test1() {
        return CompletableFuture.supplyAsync(() -> {
            ThreadUtil.sleep(1000);
            System.out.println("test1异步请求结果有返回");
            return "aaa";
        });
    }
    public CompletableFuture test2() {
        return CompletableFuture.supplyAsync(() -> {
            ThreadUtil.sleep(6000);
            System.out.println("test2异步请求结果有返回");
            return "bbb";
        });
    }
    public CompletableFuture test3() {
        return CompletableFuture.supplyAsync(() -> {
            ThreadUtil.sleep(3000);
            System.out.println("test3异步请求结果有返回");
            return "ccc";
        });
    }
}

 在controller层中利用CompletableFuture的get()方法获取数据,但是要加上try/catch捕获异常

@PostMapping("/login")
    public Result login() {
        System.out.println("登录验证成功");
        // 异步保存登录成功日志
        CompletableFuture future1 = iLoginLogService.test1();
        CompletableFuture future2 = iLoginLogService.test2();
        CompletableFuture future3 = iLoginLogService.test3();
        System.out.println("模拟正常执行的方法");
        String result1 = "111";
        String result2 = "222";
        String result3 = "333";
        String result = "";
        
        // 需要用try/catch来获取异步返回值
        try {
            
            // get()方法获取返回值,并设置10秒超时就放弃,直接报错
            result1 = future1.get(10, TimeUnit.SECONDS);
            result2 = future2.get(10, TimeUnit.SECONDS);
            result3 = future3.get(10, TimeUnit.SECONDS);
            System.out.println("返回结果为:" + result1);
            System.out.println("返回结果为:" + result2);
            System.out.println("返回结果为:" + result3);
            result = result1 + result2 + result3;
            // 正常返回结果
            return Result.success(200, "登录成功", result);
        } catch (Exception e) {
            System.out.println(e);
            return Result.success(501, "登录失败,异常错误");
        }
    }

 结果可以看到如下图所示,异步线程同时执行,但最后返回结果是等所有线程执行完成后,再返回,这就是有回调的异步操作:

Springboot利用CompletableFuture异步执行线程(有回调和无回调),第2张

五、异步执行错误异常示例

    // 无回调
    @Async
    public CompletableFuture test() {
        return CompletableFuture.runAsync(() -> {
            // 模拟第三方请求加载时间
            Thread.sleep(1000);
        
            // 异常错误模拟
            Integer test = 1/0;
            System.out.println("异步请求开始执行完成");
        });
    }

 结果就是完全不影响接口执行,且打印出报错信息,如下:

Springboot利用CompletableFuture异步执行线程(有回调和无回调),第3张