概述
简介
最近对项目代码进行优化,遇到一个场景:一个方法对多个RPC同时发起调用,每个RPC的响应事件在200ms左右而且不受控制。
有同学使用异步进行了实现,参考网上查的一些资料,大部分是如下这种描述 资料地址
回调的核心代码如下
// 三个任务都调用完成,退出循环等待
while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
sleep(1000);
}
这种实现不够优雅,使用sleep在响应时间上存在问题。
优雅高效的实现
使用 springboot的 @Async 和 java8的 CompletableFuture
1、创建实体类,调用github的一个测试API,序列化使用jackson
package com.example.asyncmethod;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown=true)
public class User {
private String name;
private String blog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBlog() {
return blog;
}
public void setBlog(String blog) {
this.blog = blog;
}
@Override
public String toString() {
return "User [name=" + name + ", blog=" + blog + "]";
}
}
2、创建github 查询的servbice
findUser方法使用了@Async注解,表示该方法会异步执行
返回值用CompletableFuture进行封装,关于Future的理解,在这是
如果需要指定线程池将value的值设为线程池名称
接口响应时间可能会比较短,在代码中使用sleep模拟接口响应时间超过1秒
package com.example.asyncmethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.CompletableFuture;
@Service
public class GitHubLookupService {
private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);
private final RestTemplate restTemplate;
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async
public CompletableFuture<User> findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}
}
3、定义线程池
添加@EnableAsync注解启用springboot异步支持
定义ThreadPool,设置为bean,如果项目中存在多个线程池指定@Bean的name,@Primary指定默认主线程池
真实项目中建议单独创建ThreadPoolConfig配置类,添加@Confuguration注解进行配置,下面为了方便直接放在了Application中
package com.example.asyncmethod;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@SpringBootApplication
@EnableAsync
public class AsyncMethodApplication {
public static void main(String[] args) {
// close the application context to shut down the custom ExecutorService
SpringApplication.run(AsyncMethodApplication.class, args).close();
}
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("GithubLookup-");
executor.initialize();
return executor;
}
}
4、编写测试类
创建异步查找的CompletableFuture
使用 CompletableFuture.allOf().join() 等待所有任务完成
打印日志
package com.example.asyncmethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component
public class AppRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);
private final GitHubLookupService gitHubLookupService;
public AppRunner(GitHubLookupService gitHubLookupService) {
this.gitHubLookupService = gitHubLookupService;
}
@Override
public void run(String... args) throws Exception {
// Start the clock
long start = System.currentTimeMillis();
// Kick of multiple, asynchronous lookups
CompletableFuture<User> page1 = gitHubLookupService.findUser("PivotalSoftware");
CompletableFuture<User> page2 = gitHubLookupService.findUser("CloudFoundry");
CompletableFuture<User> page3 = gitHubLookupService.findUser("Spring-Projects");
// Wait until they are all done
CompletableFuture.allOf(page1,page2,page3).join();
// Print results, including elapsed time
logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
logger.info("--> " + page1.get());
logger.info("--> " + page2.get());
logger.info("--> " + page3.get());
}
}
5、启动项目查看日志
2016-09-01 10:25:21.295 INFO 17893 --- [ GithubLookup-2] hello.GitHubLookupService : Looking up CloudFoundry
2016-09-01 10:25:21.295 INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService : Looking up PivotalSoftware
2016-09-01 10:25:23.142 INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService : Looking up Spring-Projects
2016-09-01 10:25:24.281 INFO 17893 --- [ main] hello.AppRunner : Elapsed time: 2994
2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Pivotal Software, Inc., blog=https://pivotal.io]
2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/]
2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Spring, blog=https://spring.io/projects]
第3步将线程池的最大数量定义为2,执行三个任务时发现线程GithubLookup-2先打印第二个定义的任务,另一个线程GithubLookup-1先打印PivotalSoftware的,此时还有一个任务在阻塞状态,待线程可用后由GithubLookup-1执行。
如果需要比较同步情况下的耗时情况,删除findUser方法的@Async注解进行测试
实际使用中对线程池的线程数、超时时间以及队列情况根据实际情况调参
最后
以上就是现实灯泡为你收集整理的springboot使用@Async CompleteableFuture完成多个任务的异步回调的全部内容,希望文章能够帮你解决springboot使用@Async CompleteableFuture完成多个任务的异步回调所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复