Spring Boot学习笔记——异步实现@Async

一、简介

  1.   本文将简单介绍利用@Async注解实现Spring Boot的异步方法。以远程调用服务,返回信息观察返回信息时间来展现异步方法的实现
  2. @Async 是一个类或方法级别的注解,它的作用是将它注解的类或方法表明是在单独线程上运行的。

二、工程实现

    1.创建spring boot工程,命名为asynchronous-spring-boot,并用maven管理引入以下依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
</dependency>

    2.创建pojo类,用于接受返回信息后转换的对象。命名为User.java代码如下:

package com.example;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class User {
	
    private String name;
    private String blog;
	
    //以下省略set,get,toString方法
}

      @JsonIgnoreProperties 注解 是一个类注解,可以忽略类中不存在的字段,大多用于无法准确预知返回信息里的完整字段究竟是哪些的时候,可以使用该类注解。也可以写作单独对某些字段的忽略,例如:也可以写成单独忽略某字段@JsonIgnoreProperties(value={"name","age"})

    3.创建发送请求的类,命名为GitHubLookupService.java,用于远程发送请求,代码如下:

package com.example;

import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class GitHubLookupService {
	
	private static Logger log = LoggerFactory.getLogger(GitHubLookupService.class);
	
	private final RestTemplate restTemplate;
	
	public GitHubLookupService(RestTemplate restTemplate) {
		this.restTemplate = restTemplate;
	}
	
	@Async
	public CompletableFuture<User> findUser(String user) throws InterruptedException {
		log.info("user:"+user);
		String url = String.format("https://api.github.com/users/%s", user);
		User results = restTemplate.getForObject(url, User.class);
		Thread.sleep(10000);
		return CompletableFuture.completedFuture(results);
	}
}

    这里返回结果据官方说,返回值是CompletableFuture 而不使用User 是对任何异步请求最基本的尊重。这里作者不太懂?希望有人可以指出。

    4.编写执行类,命名为AppRunner.java 代码如下:

    package com.example;

import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements CommandLineRunner{
	
	private static Logger log = LoggerFactory.getLogger(AppRunner.class);
	private final GitHubLookupService gitHubLookupService;
	
	public AppRunner(GitHubLookupService gitHubLookupService) {
		this.gitHubLookupService = gitHubLookupService;
	}
	
	@Override
	public void run(String... args) throws Exception {
		long start = System.currentTimeMillis();
		
		CompletableFuture<User> page1 = gitHubLookupService.findUser("PivotalSoftware");
		CompletableFuture<User> page2 = gitHubLookupService.findUser("CloudFoundry");
		CompletableFuture<User> page3 = gitHubLookupService.findUser("Spring-Projects");
		
		CompletableFuture.allOf(page1,page2,page3).join();
		
		log.info("运行时间 :" + (System.currentTimeMillis() - start));
		log.info(" --> " + page1.get());
		log.info(" --> " + page2.get());
		log.info(" --> " + page3.get());
	}

}

可以看到,run方法里实际上就是写了当3个任务都完成时所需要的时间。(allOf方法指,当参数内所有线程都完成后置为完成状态,与之对应的是anyOf方法)

    5.修改启动类,加入Bean(这里作者修改的是AsynchronousSpringBootApplication.java),代码如下:

package com.example;

import java.util.concurrent.Executor;

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 org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableAsync
public class AsynchronousSpringBootApplication {

	public static void main(String[] args) {
		SpringApplication.run(AsynchronousSpringBootApplication.class, args).close();
	}
	
        //重写线程池
	@Bean
	public Executor taskExecutor() {//线程池
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(2);//最小线程数
		executor.setMaxPoolSize(3);//最大线程数
		executor.setKeepAliveSeconds(200);//空闲时间以秒为单位,默认时间60s
		executor.setQueueCapacity(500);//等待队列数
		executor.setThreadNamePrefix("GithubLookup-");//线程名称
		executor.initialize();
		return executor;
	}
	
	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
}

官方文档说,Spring 在启动时会自动搜索taskExecutor方法,这个方法名称是特定的方法名,具体作用是设置线程池属性。如果没有写@Bean的taskExecutor方法,Spring 在启动时会自动生成SimpleAsyncTaskExecutor的线程池。

  • SimpleAsyncTaskExecutor:每次启动任务时,都会启动一个新的线程,不会复用已经启用过的线程。拥有最大并发数量。如果线程数达到最大并发数量,会处于等待状态,直到有某个线程执行完毕后才会执行等待中的任务。
  • ThreadPoolTaskExecutor:启动时会直接启动最小线程数,即如果任务小于最小线程数,也会有空闲线程启动;当任务大于最小线程数时,会把超出的任务放在队列中等待,直到线程空闲时才会从线程中取出队列。

  下面简单介绍一下ThreadPoolTaskExecutor中的几个属性:

  • 最小线程数:执行任务时的最小并发量,超出这部分就需要放到队列里去等待;小于时会产生空闲的线程。
  • 最大线程数:当线程数大于最小线程数并且队列满时,会开启;当线程数大于最小线程数并且队列满是,并且超出最大线程数会抛出异常。
  • 空闲时间:空闲时间以秒为单位,默认时间60s,当线程达到空闲时间,该线程会自动退出。
  • 等待队列数:即队列容量。

    6.启动项目观察结果,效果如下:

可以观察到,线程数并发量以两个启动。运行时间时小于3个任务的总等待时间的。如果无法理解效果可以把@Async注释掉再看效果,结果如下:

可以看到,运行时间明显增加了很多。

三、参考:

https://spring.io/guides/gs/async-method/

猜你喜欢

转载自blog.csdn.net/notMoonHeart/article/details/86130813