一、使用场景
以用户登录接口举例
1.验证账号密码, 成功 耗时 300ms
2.1.验证成功后,记录相关登录信息 耗时 100ms
2.2.验证成功后,获取系统内消息通知 耗时 300ms
2.3.验证成功后,获取首页推送内容 耗时 1000ms
3.返回、登录信息、消息通知、首页推送
如果接口中,每个步骤都是同步处理,总共就需要 1700s = 300ms + 100ms + 300ms + 1000ms
现在可以将步骤 2.1、 2.2、2.3, 开启异步线程,同时处理,(暂不考虑开启线程的耗时)
也就是意味步骤 2.1、 2.2、2.3,任务总耗时就是步骤2.3的耗时1000ms
再加上1步骤的验证耗时 300ms
即开启异步后,总共耗时 1300ms = 1000ms + 300ms, 足足减少了400ms
如果用户登录并发量大,异步处理可以大大提高吞吐量
注意:开启线程数量也不是越多越好,到达一定最大数量后,就只能等待已经开启的线程被释放,如果一个登录开启了大量线程,则会导致其他功能"无线程可用", 需要等待排队
这时可以自定义线程池,限制最大开启数量
二、实现简介
2.1. @Async
@Async 可以开启新线程,使用时需要在项目启动类添加 @EnableAsync
调用方和被调用方,不能在同一个类
,否则不起作用
2.2 Future
Future.get()执行完成后, 返回结果; 调用时执行还没有完成,则会阻塞线程等待
Future.get(long timeout,TimeUnit unit)执行完成后, 返回结果; 设置等待超时时间
Future.cancel(boolean mayInterruptIfRunning) 停止任务,
如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败,返回false
调用cancel时,如果调用成功,而此任务尚未启动,则此任务将永不运行
如果任务正在执行,mayInterruptIfRunning 参数决定了是否向正在执行任务的线程发出中断
Future.isDone()是否执行完成
Future.isCancel()是否执行取消
2.3 AsyncResult 填充异步处理返回结果
三、代码示例
3.1异步任务
package com.aaa.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Slf4j
@Component
public class TestHandler {
@Async
public Future<Integer> test(Integer i, Long millis){
try {
log.debug("异步测试-睡眠-开始, i:{}, 时间:{}", i, millis);
Thread.sleep(millis);
log.debug("异步测试-睡眠-结束, i:{}, 时间:{}", i, millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<>(i);
}
}
3.1 调用
package com.aaa.controller;
import com.alibaba.fastjson.JSONObject;
import com.aaa.component.RobotExecuteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.Future;
@Slf4j
@RestController
@RequestMapping("/v1/test")
public class TestController {
@Autowired
private TestHandler testHandler;
@PostMapping("/asyncExecute")
public Integer asyncExecute() {
log.debug("异步测试-开始");
Future<Integer> future1 = testHandler.test(1,2000L);
log.debug("异步测试-返回值 future1 :{}", JSONObject.toJSONString(future1));
Future<Integer> future2 = testHandler.test(2,1000L);
log.debug("异步测试-返回值 future2:{}", JSONObject.toJSONString(future2));
Future<Integer> future3 = testHandler.test(3,4000L);
log.debug("异步测试-返回值 future3:{}", JSONObject.toJSONString(future3));
try {
Integer i1 = future1.get();
log.debug("异步测试-返回值future1 get:{}", JSONObject.toJSONString(i1));
Integer i2 = future2.get();
log.debug("异步测试-返回值future1 get:{}", JSONObject.toJSONString(i1));
Integer i3 = future3.get();
log.debug("异步测试-返回值future3 get:{}", JSONObject.toJSONString(i3));
Integer sum = i1 + i2 + i3;
log.debug("异步测试-结束, 求和结果:{}",sum);
return sum;
} catch (Exception e) {
return -1;
}
}
}
3.1 注意
调用异步任务,应该是全部先调用, 后面获取结果
Future future1 = testHandler.test(1,2000L);
Future future2 = testHandler.test(2,1000L);
Future future3 = testHandler.test(3,4000L);
future1.get();
future2.get();
future3.get();
如果写成调用一个异步方法,紧跟着获取结果,主线程就会被阻塞, 这种写法是不可取的
Future future1 = testHandler.test(1,2000L);
future1.get();
Future future2 = testHandler.test(2,1000L);
future2.get();
Future future3 = testHandler.test(3,4000L);
future3.get();