SpringBoot
(Spring)中使用注解方式进行多线程异步(学习笔记2020.3.30)以前的项目实现异步多线程大多是使用代码配置好线程池进行代码方式调用。
而现在
Spring
提供了注解方式开启异步处理。(Annotation
支持调度和异步执行)要启用对
@Async
注释的支持,可以将@EnableAsync
添加到其中一个@Configuration
classes 中。
SpringBoot
项目 <parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.1.RELEASEversion>
parent>
<artifactId>springboot-asynchronousartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
@SpringBootApplication
@EnableAsync //启用异步
public class ApplicationAsynchronous {
public static void main(String[] args) {
SpringApplication.run(ApplicationAsynchronous.class,args);
}
}
里面有一个是注解标记的异步方法, 一个是同步方法, 然后进行调用看调用顺序与线程名称。
@Component
public class Asynchronous {
@Async
public void asynchronous1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("异步睡2秒后执行"+name+"线程名称:"+Thread.currentThread().getName());
}
public void commonMethod1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("普通睡2秒后执行"+name+"线程名称:"+Thread.currentThread().getName());
}
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsynchronousTest {
@Autowired
private Asynchronous asynchronous;
@Test
public void asynchronousTest() throws InterruptedException {
System.out.println("异步方法准备执行前");
asynchronous.asynchronous1("张韶涵1");
System.out.println("异步方法执行后");
asynchronous.commonMethod1("张韶涵2");
System.out.println("同步后方法准备执行后");
}
}
输出的结果为: (可以看出异步方法执行了并不用等待返回结果, 在执行下一步)
异步方法准备执行前
异步方法执行后
普通睡2秒后执行张韶涵2线程名称:main
同步后方法准备执行后
异步睡2秒后执行张韶涵1线程名称:task-1
因为异步的原因,程序并没有被
sleep
方法阻塞,这就是异步调用的好处。同时异步方法内部会新启一个线程来执行,这里线程名称为task - 1默认情况下的异步线程池配置使得线程不能被重用,每次调用异步方法都会新建一个线程,我们可以自己定义异步线程池来优化。
application.yml
修改application文件:
spring:
task:
execution:
pool:
max-size: 32 #最大线程数
core-size: 32 #核心线程数, 默认8
keep-alive: 10s #线程最大空闲时间, 默认值60
allow-core-thread-timeout: true #允许核心线程超时 默认值true
queue-capacity: 100 #缓冲队列大小
thread-name-prefix: mythread- #线程名
shutdown:
await-termination: true #是否等待所有线程执行完毕才关闭线程池,默认值为false。
await-termination-period: 60 #waitForTasksToCompleteOnShutdown的等待的时长,默认值为0,即不等待。
输出的结果为:
异步方法准备执行前
异步方法执行后
普通睡2秒后执行张韶涵2线程名称:main
同步后方法准备执行后
异步睡2秒后执行张韶涵1线程名称:mythread-1
如果异步方法具有返回值的话,需要使用
Future
来接收回调值。 修改asynchronous1
方法
@Async
public Future<String> asynchronous1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("异步睡2秒后执行"+name+"线程名称:"+Thread.currentThread().getName());
return new AsyncResult<>("异步返回结果"+name);
}
运行测试:
@Test //
public void asynchronousTest() throws Exception {
System.out.println("异步方法准备执行前");
String name = asynchronous.asynchronous1("张韶涵1").get();
System.out.println("异步方法执行后"+name);
asynchronous.commonMethod1("张韶涵2");
System.out.println("同步后方法准备执行后");
}
结果: 可以看出调用了
get()
后线程会阻塞到获取执行结果, 在往下执行。
get
还有一个get(long timeout, TimeUnit unit)
重载方法,我们可以通过这个重载方法设置超时时间,即异步方法在设定时间内没有返回值的话,直接抛出java.util.concurrent.TimeoutException
异常。
异步方法准备执行前
异步睡2秒后执行张韶涵1线程名称:mythread-1
异步方法执行后异步返回结果张韶涵1
普通睡2秒后执行张韶涵2线程名称:main
同步后方法准备执行后
比如: 设置10秒后没有返回结果叫抛出异常!
String name = asynchronous.asynchronous1("张韶涵1").get(10, TimeUnit.SECONDS);
@Configuration
public class AsyncPoolConfig {
@Bean
public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(25);
executor.setKeepAliveSeconds(200);
executor.setThreadNamePrefix("mythread-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
//当没有线程可以被使用时的处理策略(拒绝任务),默认策略为abortPolicy
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
CallerRunsPolicy
:用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。AbortPolicy
:直接抛出java.util.concurrent.RejectedExecutionException
异常。DiscardOldestPolicy
:当线程池中的数量等于最大线程数时、抛弃线程池中最后一个要执行的任务,并执行新传入的任务。DiscardPolicy
:当线程池中的数量等于最大线程数时,不做任何动作。要使用该线程池,只需要在
@Async
注解上指定线程池Bean名称即可:
@Async("asyncThreadPoolTaskExecutor")
public Future<String> asynchronous1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("异步睡2秒后执行"+name+"线程名称:"+Thread.currentThread().getName());
return new AsyncResult<>("异步返回结果"+name);
}
1