前言
在SpringBoot中使用异步调用是很简单的,只需要使用@Async注解即可实现方法的异步调用。
简单异步
启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.qn;
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Arrays;
@MapperScan("com.qn.mapper") @SpringBootApplication @ServletComponentScan @EnableAsync public class SpringbootDemoApplication {
public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); }
@Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { System.out.println("Let's inspect the beans provided by Spring Boot:"); String[] beanNames = ctx.getBeanDefinitionNames(); Arrays.sort(beanNames); for (String beanName : beanNames) { System.out.println(beanName); } }; } }
|
Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.qn.service;
import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service;
@Service public class AsyncService { @Async public void asyncEvent() throws InterruptedException { Thread.sleep(1000); }
public void syncEvent() throws InterruptedException { Thread.sleep(1000); } }
|
Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.qn.service;
import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest @RunWith(SpringRunner.class) class AccountServiceTest {
@Autowired AccountService accountService;
@Test void getAll() { accountService.getAll().forEach(account -> System.out.println(account.toString())); } }
|
注意
在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。
调用的异步方法,不能为同一个类的方法,简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。
异步线程池
自定义线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.qn.config.AsnycConfig;
import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@SpringBootConfiguration public class TaskExecutorConfig { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(20); taskExecutor.setMaxPoolSize(200); taskExecutor.setQueueCapacity(25); taskExecutor.setKeepAliveSeconds(200); taskExecutor.setThreadNamePrefix("oKong-"); taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.setAwaitTerminationSeconds(60); taskExecutor.initialize(); return taskExecutor; } }
|
service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.qn.service;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
@Service public class AsyncTaskService {
@Async("asyncPoolTaskExecutor") public Future<String> asyncEvent() throws InterruptedException { Thread.sleep(1000); System.out.println("异步方法内部线程名称:{}!" + Thread.currentThread().getName()); return new AsyncResult<>("异步方法返回值"); }
public void syncEvent() throws InterruptedException { Thread.sleep(1000); } }
|
Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.qn.service;
import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.Future;
@SpringBootTest @RunWith(SpringRunner.class) class AsyncTaskServiceTest { private AsyncTaskService asyncTaskService;
public String doAsync() throws InterruptedException { long start = System.currentTimeMillis(); System.out.println("方法执行开始:{}" + start); asyncTaskService.syncEvent();
long syncTime = System.currentTimeMillis(); System.out.println("同步方法用时:{}" + (syncTime - start)); Future<String> doFutrue = asyncTaskService.asyncEvent(); while(true) { if(doFutrue.isDone()) { break; } Thread.sleep(100); } long asyncTime = System.currentTimeMillis(); System.out.println("异步方法用时:{}" + (asyncTime - syncTime)); System.out.println("方法执行完成:{}!" + asyncTime); return "async!!!"; }
}
|
注意
对于一些需要异步回调的函数,不能无期限的等待下去,所以一般上需要设置超时时间,超时后可将线程释放,而不至于一直堵塞而占用资源。
配置future超时:
1 2 3
| /get方法会一直堵塞,直到等待执行完成才返回
String result = doFutrue.get(60, TimeUnit.SECONDS);
|