async spring 默认线程池_SpringBoot 开发实践(6):@Async 异步执行方法及配置自定义线程池...

本文介绍了SpringBoot中使用@Async进行异步方法执行,以提高程序效率,避免耗时操作阻塞主线程。通过示例展示了如何配置自定义线程池,以应对高并发场景,保证服务稳定性。
摘要由CSDN通过智能技术生成

前言

SpringBoot 中的方法调用,默认是单线程顺序执行的。但是在开发中我们可能会存在这样一些场景,例如发送邮件或者记录日志等,这些操作往往比较耗时,但是又不是主业务中跟业务相关的内容。这种场景我们就可以选择使用 @Async 异步方法执行,即用其它线程来异步执行某些耗时操作,从而节省主线程的运行等待时间。

使用 @Async 异步执行方法

想要使方法异步执行非常简单,简单来说,只需要在需要异步执行的方法上添加 @Async 注解即可。

编写一个 @Service 服务类,模拟耗时操作。在方法的前后,我们打上开始和结束日志,并输出线程名。

@Service

public class AsyncServiceImpl implements AsyncService {

private static final Logger LOG = LoggerFactory.getLogger(AsyncServiceImpl.class);

@Async

@Override

public void printLog1() {

LOG.info("printLog1 开始执行 -> Thread name is: {}", Thread.currentThread().getName());

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

LOG.info("printLog1 执行完毕 -> Thread name is: {}", Thread.currentThread().getName());

}

}

@Async: 表明该方法异步执行。

注意: 异步方法和调用该异步方法的方法不能放在同一个类中,否则 @Async 注解将失效。例如,方法 A 和异步方法 B 都在同一个类中,那么 A 中调用 B 时,B 还是会按照单线程来运行。解决方法就是,将 A、B 拆开,放在两个类中。

编写一个定时任务,定时执行该方法。

@Component

public class SchedulerTask {

private static final Logger LOG = LoggerFactory.getLogger(SchedulerTask.class);

@Autowired

private AsyncService asyncService;

/**

* 每秒执行一次

*/

@Scheduled(cron = "*/5 * * * * ?")

public void scheduler1() {

LOG.info("scheduler1 开始执行");

asyncService.printLog1();

LOG.info("scheduler1 执行完毕");

}

}

最后,别忘了在 Application 入口类上打上 @EnableAsync 注解,用于开始异步执行功能。

@SpringBootApplication

@EnableScheduling

@EnableAsync

public class AsyncExecutionApplication {

public static void main(String[] args) {

SpringApplication.run(AsyncExecutionApplication.class, args);

}

}

启动程序,我们可以看到如下日志:

2020-06-22 21:57:30.001 INFO 57067 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler1 开始执行

2020-06-22 21:57:30.018 INFO 57067 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler1 执行完毕

2020-06-22 21:57:30.019 INFO 57067 --- [TaskExecutor-1] c.i.s.a.service.impl.AsyncServiceImpl : printLog1 开始执行 -> Thread name is: SimpleAsyncTaskExecutor-1

2020-06-22 21:57:33.020 INFO 57067 --- [TaskExecutor-1] c.i.s.a.service.impl.AsyncServiceImpl : printLog1 执行完毕 -> Thread name is: SimpleAsyncTaskExecutor-1

2020-06-22 21:57:35.003 INFO 57067 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler1 开始执行

2020-06-22 21:57:35.004 INFO 57067 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler1 执行完毕

2020-06-22 21:57:35.004 INFO 57067 --- [TaskExecutor-2] c.i.s.a.service.impl.AsyncServiceImpl : printLog1 开始执行 -> Thread name is: SimpleAsyncTaskExecutor-2

2020-06-22 21:57:38.008 INFO 57067 --- [TaskExecutor-2] c.i.s.a.service.impl.AsyncServiceImpl : printLog1 执行完毕 -> Thread name is: SimpleAsyncTaskExecutor-2

2020-06-22 21:57:40.001 INFO 57067 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler1 开始执行

2020-06-22 21:57:40.001 INFO 57067 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler1 执行完毕

2020-06-22 21:57:40.002 INFO 57067 --- [TaskExecutor-3] c.i.s.a.service.impl.AsyncServiceImpl : printLog1 开始执行 -> Thread name is: SimpleAsyncTaskExecutor-3

2020-06-22 21:57:43.003 INFO 57067 --- [TaskExecutor-3] c.i.s.a.service.impl.AsyncServiceImpl : printLog1 执行完毕 -> Thread name is: SimpleAsyncTaskExecutor-3

我们可以看到,定时任务与 printLog1() 方法并发执行,说明 printLog1() 方法成功地异步执行了。

自定义线程池

从上面的日志我们可以看到,使用默认 @Async 异步执行的方法,用的是 SimpleAsyncTaskExecutor 不重用线程,每次调用都创建了一个新的线程。

默认的 @Async 虽然可以应付一般的场景,但是如果是并发量比较高的情况下,就存在一定风险了。例如开销过大、内存溢出等。为使服务运行稳定,我们可以自定义配置线程池,然后让给需要异步执行的方法指定用该线程池运行。

配置自定义线程池

创建一个 ExecutorConfig.java 配置类。

@Configuration

public class ExecutorConfig {

@Bean(name = "myExecutor")

public Executor executor() {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

//配置核心线程数

executor.setCorePoolSize(3);

//配置最大线程数

executor.setMaxPoolSize(10);

//配置队列大小

executor.setQueueCapacity(100);

//配置线程池中的线程的名称前缀

executor.setThreadNamePrefix("MyExecutor-");

// rejection-policy:当pool已经达到max size的时候,如何处理新任务

// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

//执行初始化

executor.initialize();

return executor;

}

}

@Configuration: 表名这是一个配置类。该类中打上 @Bean 注解的方法都会在 Spring 启动时被扫描运行,然后将返回的 bean 注入到 Spring 容器中。

@Bean: 该方法返回的对象将被注入到 Spring 容器中。在上面的方法中,我们将自定义配置的线程池命名为 myExecutor 交给 Spring 来管理。

给异步方法指定线程池

我们再创建一个异步方法,这次将该方法指定给我们刚刚配置好的线程池来处理。

@Async("myExecutor")

@Override

public void printLog2() {

LOG.info("printLog2 开始执行 -> Thread name is: {}", Thread.currentThread().getName());

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

LOG.info("printLog2 执行完毕 -> Thread name is: {}", Thread.currentThread().getName());

}

@Async: 将自定义线程池的名字赋值给 @Async,那么就表明该方法需要用 myExecutor 线程池来处理。

启动程序,运行结果如下:

2020-06-23 01:19:40.001 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 开始执行

2020-06-23 01:19:40.018 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 执行完毕

2020-06-23 01:19:40.018 INFO 57595 --- [ MyExecutor-1] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 开始执行 -> Thread name is: MyExecutor-1

2020-06-23 01:19:43.022 INFO 57595 --- [ MyExecutor-1] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 执行完毕 -> Thread name is: MyExecutor-1

2020-06-23 01:19:45.000 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 开始执行

2020-06-23 01:19:45.001 INFO 57595 --- [ MyExecutor-2] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 开始执行 -> Thread name is: MyExecutor-2

2020-06-23 01:19:45.001 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 执行完毕

2020-06-23 01:19:48.002 INFO 57595 --- [ MyExecutor-2] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 执行完毕 -> Thread name is: MyExecutor-2

2020-06-23 01:19:50.004 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 开始执行

2020-06-23 01:19:50.005 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 执行完毕

2020-06-23 01:19:50.005 INFO 57595 --- [ MyExecutor-3] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 开始执行 -> Thread name is: MyExecutor-3

2020-06-23 01:19:53.006 INFO 57595 --- [ MyExecutor-3] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 执行完毕 -> Thread name is: MyExecutor-3

2020-06-23 01:19:55.001 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 开始执行

2020-06-23 01:19:55.002 INFO 57595 --- [ MyExecutor-1] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 开始执行 -> Thread name is: MyExecutor-1

2020-06-23 01:19:55.002 INFO 57595 --- [ scheduling-1] c.i.s.a.scheduler.SchedulerTask : scheduler2 执行完毕

2020-06-23 01:19:58.003 INFO 57595 --- [ MyExecutor-1] c.i.s.a.service.impl.AsyncServiceImpl : printLog2 执行完毕 -> Thread name is: MyExecutor-1

可以看到,日志中输出的线程前缀名称,即为我们自定义线程池前缀的名称。

以上就是使用 @Async 异步执行及配置自定义线程池的方法。

本章代码地址:GitHub

我是因特马,一个爱分享的斜杠程序员~

欢迎关注我的公众号:一只因特马

130dc687fa2d5ca654e1beb80e30e989.png

原文作者: 一只因特马

原文链接: https://www.interhorse.cn/a/3350135757/

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值