前言
异步调用与同步调用
- 同步调用:顺序执行,通过调用返回结果再次执行下一个调用
- 异步调用:通过调用,无需等待返回结果,执行下一个调用
1. @Async讲解
其@Async
的注解代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
String value() default "";
}
注解可以使用在类型以及方法中
通过value定义其值,默认是空
一般这个注解需要配合@EnableAsync
,起源码如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Integer.MAX_VALUE;
}
主要通过该注解放置在启动类中进行配置启动
在启动类中添加如下:
@SpringbootApplication
@EnableAsync
public class Application{
public static void main(String[] args){
SrpingApplication.run(Application.class, args);
}
}
2. 用法
2.1 同步调用
从调用到返回函数结果才能执行下一步,称为同步调用
service层 代码:
public class Service{
public void test01() throws InterruptedException{
Thread.sleep(5000);
System.out.println("保存日志");
}
}
控制层代码模块:
public class Controler{
@Autowired
private Service service;
@GetMapping("/test")
public String getTest(){
try{
System.out.println("开始");
service.test01();
System.out.println("结束");
}catch(InterruptedException e){
e.prinStackTrace();
}
}
}
通过springboot的启动类启动之后
输出如下:
开始
// 此为等待5秒钟,终端不显示也不关闭
结束
2.2 异步调用
异步调用,执行函数不用等返回结果就可以执行下一步
service层 代码:
主要是添加了@Async注解标识这个方法
public class Service{
@Async
public void test01() throws InterruptedException{
Thread.sleep(500);
System.out.println("保存日志");
}
}
控制层代码模块:
通过调用service层函数
public class Controler{
@Autowired
private Service service;
@GetMapping("/test")
public String getTest(){
try{
System.out.println("开始");
service.test01();
System.out.println("结束");
}catch(InterruptedException e){
e.prinStackTrace();
}
}
}
以及在启动类中加入注解启动 @EnableAsync
@SpringbootApplication
@EnableAsync
public class Application{
public static void main(String[] args){
SrpingApplication.run(Application.class, args);
}
}
3. 自定义线程池
对于线程池的一些基本知识可看我之前的文章:
- java如何正确关闭线程以及线程池(代码实践含源码分析)
- java线程池的四种创建方式详细分析(全)
如果不指定线程池,默认使用的线程池为SimpleAsyncTaskExecutor(来一个任务就创建一个线程,不断创建线程导致CPU过高引发OOM),自带的线程池一般都有弊端,一般推荐使用ThreadPoolExecutor(明确线程池的资源,规避风险)
具体如下:
- newFixedThreadPool:定死了线程数,任务队列还是无界的,(最大线程数只有队列满了,最大线程数才会创建),所以会造成OOM
- newCachedThreadPool:没有设置最大线程数上限,创建大量的线程容易卡顿或者直接OOM
通过自定义线程池可以调整线程池的配置,更好的资源利用
@Async这个注解查找 AsyncConfigurer接口(实现类为AsyncConfigurerSupport,默认配置和方法都是空),所以可重写接口指定线程池。
- 通过实现接口AsyncConfigurer
- 继承AsyncConfigurerSupport
- 自定义TaskExecutor(替代内置任务执行器)
第三种方法:
在application.xml中定义线程池的一些变量
thread.core.size=16
thread.max.size=16
thread.queue.size=30
thread.prefix=xx-
自定义线程池如下
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
// 线程名称前缀
@Value("${thread.prefix}")
private String threadPrefix;
// 核心线程数
@Value("${thread.core.size}")
private int coreSize;
// 最大线程数
@Value("${thread.max.size}")
private int maxSize;
// 队列长度
@Value("${thread.queue.size}")
private int queueSize;
// 通过bean注解注入
@Bean("xx")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//设置线程池参数信息
taskExecutor.setCorePoolSize(coreSize);
taskExecutor.setMaxPoolSize(maxSize);
taskExecutor.setQueueCapacity(queueSize);
taskExecutor.setThreadNamePrefix(threadPrefix);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(30);
//修改拒绝策略为使用当前线程执行
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化线程池
taskExecutor.initialize();
return taskExecutor;
}
}