springboot 异步错误使用导致cpu负载过高
转载
- 在线文档
- 项目结构
1.源码克隆:git clone https://github.com/spring-guides/gs-async-method.git
2.包含两个项目initial和complete,initial可以根据文档练习完善,complete是完整项目
3.功能描述:构建查找服务,异步查询 GitHub 用户信息并通过 GitHub 的 API 检索数据
- 源码分析
1.POM依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.入口类
//
@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();
}
/**
* 这段代码定义了一个 Executor 对象,该对象是使用 ThreadPoolTaskExecutor 类创建的线程池。Executor 是一个接口,它定义了一种方法来异步地执行任务。
*
* ThreadPoolTaskExecutor 是一个实现了 Executor 接口的类,它使用线程池来执行任务。线程池是一种管理线程的机制,它提供了一种可伸缩的线程池,用于在应用程序中执行异步任务。
*
* 在这段代码中,我们设置了线程池的核心线程数(corePoolSize)和最大线程数(maxPoolSize)为 2,以及队列的容量(queueCapacity)为 500。我们还为线程池中的线程设置了名称前缀(threadNamePrefix)。最后,我们调用 initialize() 方法来初始化线程池。
*
* 这段代码还使用了 @Bean 注解,该注解表示此方法将会在应用程序上下文中生成一个 bean。这意味着,你可以在应用程序的其他地方使用这个 Executor 对象。
*
* 例如,你可以在另一个类中注入这个 Executor 对象并使用它来异步地执行任务:
* @Autowired
* private Executor taskExecutor;
*
* public void doSomething() {
* taskExecutor.execute(new Runnable() {
* @Override
* public void run() {
* // do something
* }
* });
* }
* @return
*/
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("GithubLookup-");
executor.initialize();
return executor;
}
}
3.接口类
@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();
}
/**
* 这段代码定义了一个名为 findUser() 的方法,它使用 RestTemplate 发送一个 GET 请求到 GitHub API,获取指定用户的信息并返回。方法的返回值是 CompletableFuture 类型,它可以用于执行异步操作。
*
* @Async 注解告诉 Spring 在单独的线程中执行 findUser() 方法。这意味着 findUser() 方法的调用将立即返回,而不等待方法实际完成。你可以在其他代码中继续执行其他任务,而无需等待 findUser() 方法的响应返回。
*
* findUser() 方法返回的 CompletableFuture 对象可以用于获取方法执行的结果。例如,你可以使用 thenApply() 方法在方法执行完成后对结果进行处理:
* CompletableFuture<User> future = findUser("some-user");
* future.thenApply(user -> {
* // Do something with the result
* return user;
* });
* @param user
* @return
* @throws InterruptedException
*/
@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);
}
}
4.测试类
/**
* 这段代码定义了一个名为 AppRunner 的组件,它实现了 CommandLineRunner 接口,因此在 Spring 应用程序启动时会执行 run() 方法。
*
* AppRunner 类持有一个名为 gitHubLookupService 的 GitHubLookupService 类型的引用,并在构造函数中将其注入。GitHubLookupService 类是一个用于查找 GitHub 用户信息的服务。
*
* 在 run() 方法中,代码首先记录了当前时间,然后使用 gitHubLookupService 发出三个异步请求来查找三个 GitHub 用户。请求返回的是 CompletableFuture 类型的对象,这些对象会被存储在 page1、page2 和 page3 中。
*
* 接下来,代码调用 allOf() 方法,该方法接受多个 CompletableFuture 对象并返回一个新的 CompletableFuture,该 CompletableFuture 将在所有传入的 CompletableFuture 对象都完成后完成。allOf() 方法的返回值被加入,这意味着程序将在所有请求完成后再继续执行。
*
* 最后,代码打印了每个请求的结果以及整个操作所用的时间。get() 方法用于获取 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());
}
}
- 项目演示
前两个调用发生在不同的线程 ( GithubLookup-2, GithubLookup-1) 中,第三个调用被暂停,直到两个线程之一可用为止。
要比较在没有异步功能的情况下这需要多长时间,请尝试注释掉@Async注释并再次运行该服务。总耗用时间应该会显着增加,
因为每个查询至少需要一秒钟。
本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。