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

 

posted on 2021-10-15 11:48  热爱足球的真祺怪  阅读(432)  评论(0)    收藏  举报

导航