Async注解的简单使用记录
需求描述
报表页面有许多下拉,下拉是根据数据去重展示的,有的表数据量会比较大,采用redis缓存。把获取的下拉存到redis中,每次请求先从redis获取对应的下拉值,获取到则不再执行查询sql,相反则执行sql查询并缓存。
现提出一个优化,有的表需要每天更新下拉值。目前的功能不满足的点,一是key的过期时间是24小时,这样每次过期之后会存在下拉没值的情况,因为在获取且时间比较长。二是如果多次请求有可能会造成系统资源的浪费。
根据会议讨论,采用异步获取下拉,且手动加锁的机制(锁的失效时间是12小时,避免死锁),key的过期时间不设置,也就是永久有效,每次请求先判断key的创建时间,如果大于一天,先加锁(避免多次请求),并异步获取下拉值,这样在获取的时候,实际下拉还是有值的,只是没有更新最新值,当下拉获取结束则会更新,并解锁。
@Async
@Async
先会用,再研究。
@Slf4j
@Component
public class AsyncTask {
@Async
public Future<String> doTask(int num) {
try {
log.info(Thread.currentThread().getName() + "异步");
} catch (Exception e) {
log.error(e.getMessage());
}
return new AsyncResult<>("hi: " + num);
}
}
doTask里面写对应的异步代码。
如果需要返回值,用Future,不需要的话直接用void就可以了。
注意:@Async注解,返回值只能是void或Future
@Autowired
private AsyncTask asyncTask;
通过Autowired注入。
@GetMapping("/async")
public void asyncTest(@RequestParam(value = "num") Integer num) {
for (int i = 0 ; i < num ; i++) {
Future<String> future = asyncTask.doTask(i);
}
}
直接调用就可以了。
可以看到实现是非常简单的,只需要@Async注解就实现了基本的异步需求,不需要配置线程池,使用起来很方便。
测试启动
试试十个线程。
2021-10-15 11:19:42.380 INFO 12012 --- [TaskExecutor-50] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-50异步 2021-10-15 11:19:42.380 INFO 12012 --- [TaskExecutor-49] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-49异步 2021-10-15 11:19:42.381 INFO 12012 --- [TaskExecutor-48] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-48异步 2021-10-15 11:19:42.381 INFO 12012 --- [TaskExecutor-47] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-47异步 2021-10-15 11:19:42.381 INFO 12012 --- [TaskExecutor-46] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-46异步 2021-10-15 11:19:42.381 INFO 12012 --- [TaskExecutor-45] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-45异步 2021-10-15 11:19:42.381 INFO 12012 --- [TaskExecutor-43] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-43异步 2021-10-15 11:19:42.381 INFO 12012 --- [TaskExecutor-44] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-44异步 2021-10-15 11:19:42.382 INFO 12012 --- [TaskExecutor-42] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-42异步 2021-10-15 11:19:42.382 INFO 12012 --- [TaskExecutor-41] c.security.dataFactory.task.AsyncTask : SimpleAsyncTaskExecutor-41异步
可以看到线程号一直在增加,那为什么会一直增加,有没有线程池来管理。
此处引用两篇博客。
https://www.cnblogs.com/hegeainiyo/p/13362476.html
https://www.whywhy.vip/archives/127
不过两个博客跟我的源码都不太一样,大概是版本不一致吧。
简单来说,@Async注解有一个参数value,可以不传值。不传的话默认采用这个线程池SimpleAsyncTaskExecutor。但是这个东西不是真正的线程池,而是每次都会新启一个线程去执行任务。所以上面看到的线程号是一直在增加的。并不会重用线程。
再贴一篇文章。
https://www.cnblogs.com/heyouxin/p/14745804.html
文中说,如果达到并发数量最大的时候,再启动新线程会一直等有任务结束才会新启动线程执行,可能会有坑,所以最好还是自定义线程池更好?因为我并发做得很少,这方面知识比较匮乏,仅供参考。
自定义线程池
那么我们是不是也可以不采用SimpleAsyncTaskExecutor,转而自定义线程池呢,当然是可以的。实现如下。
@Slf4j
@Component
public class AsyncTask {
@Async(value = "qiujiaqiThreadPool")
public Future<String> doTask(int num) {
try {
log.info(Thread.currentThread().getName() + "异步");
} catch (Exception e) {
log.error(e.getMessage());
}
return new AsyncResult<>("hi: " + num);
}
@Bean("qiujiaqiThreadPool")
public Executor qiujiaqiThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程大小
executor.setCorePoolSize(8);
// 最大线程大小
executor.setMaxPoolSize(12);
// 队列容量
executor.setQueueCapacity(100);
// 活跃时间
executor.setKeepAliveSeconds(60);
// 线程名字前缀
executor.setThreadNamePrefix("qiujiaqiThreadPool-");
executor.initialize();
return executor;
}
}
只需要自定义一个线程池,加上@Bean注解,在调用@Async注解的时候,value=定义的线程池名字就可以了。
2021-10-15 11:38:39.173 INFO 14984 --- [aqiThreadPool-2] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-2异步 2021-10-15 11:38:39.173 INFO 14984 --- [aqiThreadPool-1] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-1异步 2021-10-15 11:38:39.173 INFO 14984 --- [aqiThreadPool-3] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-3异步 2021-10-15 11:38:39.173 INFO 14984 --- [aqiThreadPool-4] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-4异步 2021-10-15 11:38:39.174 INFO 14984 --- [aqiThreadPool-6] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-6异步 2021-10-15 11:38:39.174 INFO 14984 --- [aqiThreadPool-5] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-5异步 2021-10-15 11:38:39.175 INFO 14984 --- [aqiThreadPool-8] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-8异步 2021-10-15 11:38:39.175 INFO 14984 --- [aqiThreadPool-7] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-7异步 2021-10-15 11:38:39.175 INFO 14984 --- [aqiThreadPool-2] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-2异步 2021-10-15 11:38:39.175 INFO 14984 --- [aqiThreadPool-2] c.security.dataFactory.task.AsyncTask : qiujiaqiThreadPool-2异步
可以看到,线程号没有超过8的,因为我们定义的核心线程数为8,最大线程虽然是12,但是我们线程并没有超过容量,所以最大就是8。
提到@Bean,顺带提一下@Bean和@Component的相同点和区别
相同点:
都是注册bean到spring容器中,交由spring容器管理
@Component是作用在类上的,
@Bean自定义性更强,可以实现一些自定义加载类
参考文章 https://www.jianshu.com/p/3fbfbb843b63
浙公网安备 33010602011771号