SpringBoot中如何优雅的使用多线程

智数先锋说
• 阅读 945

在 SpringBoot 应用中,经常会遇到在一个接口中,同时做事情1,事情2,事情3,如果同步执行的话,则本次接口时间取决于事情1 2 3执行时间之和;如果三件事同时执行,则本次接口时间取决于事情1 2 3执行时间最长的那个,合理使用多线程,可以大大缩短接口时间。那么在 SpringBoot 应用中如何优雅的使用多线程呢?

快速使用
SpringBoot应用中需要添加@EnableAsync注解,来开启异步调用,一般还会配置一个线程池,异步的方法交给特定的线程池完成,如下:

@Configuration
@EnableAsync
public class AsyncConfiguration {

    @Bean("doSomethingExecutor")
    public Executor doSomethingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(10);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(20);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("do-something-");
        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        executor.initialize();
        return executor;
    }

}

AsyncService

@Slf4j
@Service
public class AsyncService {

    // 指定使用beanname为doSomethingExecutor的线程池
    @Async("doSomethingExecutor")
    public String doSomething(String message) {
        log.info("do something, message={}", message);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            log.error("do something error: ", e);
        }
        return message;
    }
}

AsyncController

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/open/something")
    public String something() {
        int count = 10;
        for (int i = 0; i < count; i++) {
            asyncService.doSomething("index = " + i);
        }
        lon
        return "success";
    }
}

访问:127.0.0.1:8080/open/something,日志如下

2020-04-19 23:42:42.486 INFO 21168 — [io-8200-exec-17] x.g.b.system.controller.AsyncController : do something end, time 8 milliseconds
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-1] x.gits.boot.system.service.AsyncService : do something, message=index = 0
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-5] x.gits.boot.system.service.AsyncService : do something, message=index = 4
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-4] x.gits.boot.system.service.AsyncService : do something, message=index = 3
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-6] x.gits.boot.system.service.AsyncService : do something, message=index = 5
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-9] x.gits.boot.system.service.AsyncService : do something, message=index = 8
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-8] x.gits.boot.system.service.AsyncService : do something, message=index = 7
2020-04-19 23:42:42.488 INFO 21168 — [do-something-10] x.gits.boot.system.service.AsyncService : do something, message=index = 9
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-7] x.gits.boot.system.service.AsyncService : do something, message=index = 6
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-2] x.gits.boot.system.service.AsyncService : do something, message=index = 1
2020-04-19 23:42:42.488 INFO 21168 — [ do-something-3] x.gits.boot.system.service.AsyncService : do something, message=index = 2

由此可见已经达到异步执行的效果了,并且使用到了咱们配置的线程池

获取异步方法返回值

当异步方法有返回值时,如何获取异步方法执行的返回结果呢?这时需要异步调用的方法带有返回值CompletableFuture。
CompletableFuture是对Feature的增强,Feature只能处理简单的异步任务,而CompletableFuture可以将多个异步任务进行复杂的组合。如下:

AsyncService

@Slf4j
@Service
public class AsyncService {

    @Async("doSomethingExecutor")
    public CompletableFuture<String> doSomething1(String message) throws InterruptedException {
        log.info("do something1: {}", message);
        Thread.sleep(1000);
        return CompletableFuture.completedFuture("do something1: " + message);
    }

    @Async("doSomethingExecutor")
    public CompletableFuture<String> doSomething2(String message) throws InterruptedException {
        log.info("do something2: {}", message);
        Thread.sleep(1000);
        return CompletableFuture.completedFuture("; do something2: " + message);
    }

    @Async("doSomethingExecutor")
    public CompletableFuture<String> doSomething3(String message) throws InterruptedException {
        log.info("do something3: {}", message);
        Thread.sleep(1000);
        return CompletableFuture.completedFuture("; do something3: " + message);
    }
}

AsyncController

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/open/somethings")
    public String somethings() {
        CompletableFuture<String> createOrder = asyncService.doSomething1("create order");
        CompletableFuture<String> reduceAccount = asyncService.doSomething2("reduce account");
        CompletableFuture<String> saveLog = asyncService.doSomething3("save log");

        // 等待所有任务都执行完
        CompletableFuture.allOf(createOrder, reduceAccount, saveLog).join();
        // 获取每个任务的返回结果
        String result = createOrder.get() + reduceAccount.get() + saveLog.get();
        return result;
    }
}

注意事项

@Async注解会在以下几个场景失效,也就是说明明使用了@Async注解,但就没有走多线程

1.异步方法使用static关键词修饰
2.异步类不是一个Spring容器的bean(一般使用注解@Component和@Service,并且能被Spring扫描到)
3.SpringBoot应用中没有添加@EnableAsync注解
4.在同一个类中,一个方法调用另外一个有@Async注解的方法,注解不会生效。原因是@Async注解的方法,是在代理类中执行的

点赞
收藏
评论区
推荐文章
Promise入门
ES6为什么推出PromisePromise是ES6新增的引用类型,让我们的异步逻辑代码更加优雅。异步就相当于你会了影分身,本来你只能同一时间做一个事情,但是当你有了分身之后,你可以和他同一时间做不同的事情。所以,异步增加了我们事情完成的效率,这也就是我们常说的避免进程等待一个长时间的线程操作,同时执行,减少耗时,增加性能。异步是JavaScript的基
@Transaction注解的失效场景
事情是这样,最近在实现一个需求的时候,有一个定时异步任务会捞取主表的数据并置为处理中(为了防止任务执行时间过长,下次任务执行把本次数据重复捞取),然后根据主表关联明细表数据,然后将明细表数据进行组装,等待所有明细数据处理完成之后,将主表状态置为完成;大概当时的代码示例(只是截取部分)如下:
Wesley13 Wesley13
3年前
@Scheduled注解
1概述@Scheduled注解是springboot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行.注意需要配合@EnableScheduling使用,配置@Scheduled主要有三种配置执行时间的方式,cron,fixedRate,fixedDelay.2croncron是
Stella981 Stella981
3年前
Shiro 放行Swagger
一、前言在使用SpringBootShiroMybatisSwagger开发后台权限管理系统的时候,由于SpringBoot采用了Shiro框架,同时API接口文档使用的Swagger,遇到一个问题,在SpringBoot集成Shiro后,访问Swagger接口需要登陆才可以,由于在项目成型之前需要做接口测试,所以这里记录下如何在Shi
Stella981 Stella981
3年前
JavaScript同步、异步及事件循环
同步、异步JS是单线程的,每次只能做一件事情。像以下这种情况,代码会按顺序执行,这个就叫同步。console.log(1);console.log(2);console.log(3);以下代码会输出2、3、1,像这种不按顺序执行的,或者说代码执行中间有时间间隙的,叫异步。setTimeout((
Wesley13 Wesley13
3年前
Go 中的并发和并行
并行是指同一时间做多件事情,并发是指同一时间具有做多件事情的能力。在很多情况下,并发的效果比并行好,因为操作系统的硬件资源和总资源是很少而且固定的,不能无限去扩张。Go语言设计的时候应该也是推崇这种使用较少资源做更多事情的哲学。并发执行Go语言中可以使用系统功能设置可以使用的物理处理器(核),如果设置为1的话,所有的协程会在一个核上
Stella981 Stella981
3年前
Spring Boot 与 Kotlin 上传文件
如果我们做一个小型的web站,而且刚好选择的kotlin和SpringBoot技术栈,那么上传文件的必不可少了,当然,如果你做一个中大型的web站,那建议你使用云存储,能省不少事情。这篇文章就介绍怎么使用kotlin和SpringBoot上传文件构建工程如果对于构建工程还不是很熟悉的可以参考《我的第一个Kotlin应用》
Stella981 Stella981
3年前
Spring Boot filter
在SpringBoot中自定义filter本文我们将会讲解如何在SpringBoot中自定义filter并指定执行顺序。定义Filter很简单,我们只需要实现Filter接口即可,同时我们可指定@Order来确定其执行顺序,我们定义两个filter如下:@Slf4j@Component@Order(1)p
Stella981 Stella981
3年前
Spring AOP 切面编程记录日志和接口执行时间
最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx、tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特别长。知道了问题之后,就需要对查询比较慢的接口进行优化,但哪些接口需要优化、哪些不需要呢?只能通过日志里的执行时间来判断,那么如何才能知道每一个接口的执行时间呢
京东云开发者 京东云开发者
9个月前
Java定时任务大盘点:发工资也能“指日可待”
作者:京东保险孙昊宇引子:“指日可待”让我们先从一个成语开始,“指日可待”。没错,我说的就是定时任务😏。“指日可待”:为任务指定好日程,就可以安心等待任务执行。在实际场景中,我们往往需要在特定时间做某件事情,或以某个时间间隔重复某件事情,如定期备份数据、
京东云开发者 京东云开发者
7个月前
Java定时任务大盘点:发工资也能“指日可待”
作者:京东保险孙昊宇引子:“指日可待”让我们先从一个成语开始,“指日可待”。没错,我说的就是定时任务😏。“指日可待”:为任务指定好日程,就可以安心等待任务执行。在实际场景中,我们往往需要在特定时间做某件事情,或以某个时间间隔重复某件事情,如定期备份数据、
智数先锋说
智数先锋说
Lv1
愿我喜欢的人都能被好运照顾。
文章
5
粉丝
0
获赞
0