参考:解剖SpringBoot异步线程池框架_哔哩哔哩_bilibili
1、 为什么要用异步框架,它解决什么问题?
在SpringBoot的日常开发中,一般都是同步调用的。但经常有特殊业务需要做异步来处理,例如:注册新用户,送100个积分;或下单成功,发送push消息等。
(1)容错性
如果送积分出现异常,不能因为送积分而导致用户注册失败。
因为用户注册是主要功能,送积分是次要功能,即使送积分异常也要提示用户注册成功,然后后面再针对积分异常做补偿处理。
(2)提升性能
例如注册用户花了20毫秒,送积分花费50毫秒。如果同步的话,总耗时70毫秒,用异常的话,无需等待积分,故耗时20毫秒。
因此,使用异步能解决2个问题:容错性+性能。
2、简单异步调用示例
(1)开启异步任务
采用@EnableAsync来开启异步任务支持,另外需要加入@Configuration来把当前类加入springIOC容器中。
SyncConfiguration.java文件:
@Configuration
@EnableAsync
public class SyncConfiguration {
}
(2)在方法上标记异步调用
增加一个service类,用来做积分处理
@Async添加在方法上,代表该方法为异步处理
ScoreService.java文件:
public interface ScoreService {
public void addScore(); // 增加积分
}
ScoreServiceImpl.java文件:
@Service
@Slf4j
public class ScoreServiceImpl implements ScoreService {
@Async
@Override
public void addScore() {
// 模拟睡5秒,用于赠送积分处理
try {
Thread.sleep(5000);
log.info("--------------------处理积分----------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
调用测试:
@GetMapping("/sync1")
public String getAsyncScore(){
log.info("--------注册用户----------");
scoreService.addScore();
return "OK";
}
连续发送4次请求的日志信息如下:
3、为什么要给@Async自定义线程池
@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,因为它不是真正的线程池,这个类不重用线程,每次调用都会新建一个新的线程。
可以通过如上日志查看,每次打印的线程名都是[task-1],[task-2], [task-3]……递增的。
我们采用ThreadPoolTaskExecutor,其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
4、为@Async实现一个自定义线程池
(1)配置自定义线程池
SyncConfiguration.java文件:
@Configuration
@EnableAsync
public class SyncConfiguration {
@Bean(name="scorePoolTaskExecutor")
public ThreadPoolTaskExecutor getScorePoolTaskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
taskExecutor.setCorePoolSize(2);
// 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(4);
// 缓存队列
taskExecutor.setQueueCapacity(2);
// 空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(10);
// 异步方法内部线程名称
taskExecutor.setThreadNamePrefix("score-");
// 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到达就会采取的拒绝策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
(2)为@Async指定线程池的名称
ScoreService.java文件:
public interface ScoreService {
public void addScore(); // 增加积分
public void addScore2(); // 增加积分测试2
}
ScoreServiceImpl.java文件
@Service
@Slf4j
public class ScoreServiceImpl implements ScoreService {
@Async
@Override
public void addScore() {
// 模拟睡5秒,用于赠送积分处理
try {
Thread.sleep(5000);
log.info("--------------------处理积分----------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Async("scorePoolTaskExecutor")
@Override
public void addScore2() {
// 模拟睡5秒,用于赠送积分处理
try {
Thread.sleep(5000);
log.info("--------------------处理积分----------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类:
@GetMapping("/sync2")
public String getAsyncScore2(){
log.info("--------注册用户----------");
scoreService.addScore2();
return "OK";
}
连续调用8次的效果:
可以发现只有两个线程一直在重用。
6、总结
(1)异步的好处:
容错性+性能提升
(2)自定义线程池的方法:
首先,配置类中开启异步任务支持,同时配置线程池策略
其次,异步方法的@Async中指定线程池的名称