目录
背景
实现
一、异步线程配置类
二、自定义异步异常统一处理类
三、实现调用异步(无回调-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("---------------------异步请求异常捕获---------------------------------");
}
}
在需要异步的方法前加上@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, "登录成功");
}
执行完成后,我们打开控制台,可以看到, 异步请求在接口执行正常,在接口返回结果后执行完成。
这里我们多采用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, "登录失败,异常错误");
}
}
结果可以看到如下图所示,异步线程同时执行,但最后返回结果是等所有线程执行完成后,再返回,这就是有回调的异步操作:
// 无回调
@Async
public CompletableFuture test() {
return CompletableFuture.runAsync(() -> {
// 模拟第三方请求加载时间
Thread.sleep(1000);
// 异常错误模拟
Integer test = 1/0;
System.out.println("异步请求开始执行完成");
});
}
结果就是完全不影响接口执行,且打印出报错信息,如下: