背景
最近在学习线程池相关的知识点,发现线程池可以通过execute实现异步方法,然后除了线程池和消息队列能否通过其他方式来实现异步功能。最后通过万能的度娘,发现了spring中的@Async注解,只需要简单的几个步骤就可以实现异步请求,记录学习一下。
使用学习
一、搭建一个springboot 工程并且在启动类中加上@EnableAsync注解
@EnableAsync
@SpringBootApplication
public class AsyncApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncApplication.class);
}
}
二、创建service,里面有两个方法,一个加了@Async注解,一个没加,代码逻辑都差不多
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
@Async
public String sayhello() throws InterruptedException, ExecutionException {
//模拟业务处理,假设需要5s去处理业务
Thread.sleep(5000);
ExecutorService newFixedThreadPool = Executors.newSingleThreadExecutor();
Future<String> submit = newFixedThreadPool.submit(() -> {
//do someThing
log.info("invoke sayhello return:Hello!");
return "Hello!";
});
// 调用方调用后会立即返回,所以返回null
return submit.get();
}
public String sayhello2() throws InterruptedException, ExecutionException {
//模拟业务处理,假设需要5s去处理业务
Thread.sleep(5000);
ExecutorService newFixedThreadPool = Executors.newSingleThreadExecutor();
Future<String> submit = newFixedThreadPool.submit(() -> {
//do someThing
log.info("invoke sayhello2 return:Hello2!");
return "Hello2!";
});
// 需要执行完代码才会返回,所以会返回 Hello2!
return submit.get();
}
}
三、创建controller类来测试
@Slf4j
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/sayHello")
public String sayHello() throws InterruptedException, ExecutionException {
log.info("请求调用:sayHello");
String sayhello = asyncService.sayhello();
log.info("sayHello返回:" + sayhello);
log.info("============");
return sayhello;
}
@GetMapping("/sayHello2")
public String sayHello2() throws InterruptedException, ExecutionException {
log.info("请求调用:sayHello2");
String sayhello2 = asyncService.sayhello2();
log.info("sayHello2返回:" + sayhello2);
log.info("============");
return sayhello2;
}
}
(1)使用postman 调用添加了@Async注解的方法
直接返回null,不需要等Thread.sleep(5000)执行完
查看控制台打印的日志, 已经将结果返回给客户端后过了大概5000ms才执行Callable独立线程里面的打印方法
(2)使用postman 调用没有添加@Async注解的方法
postman 等待,表示方法在执行中
查看控制台打印的日志,等执行Callable子线程里面的打印方法以及返回了值之后controller再将值返回给了客户端
再看postman,获取到了值
总结:
可以用spring中的@Async注解来做异步处理,相当于在当前线程之外创建一个独立线程,可以简略的当做消息队列来做异步处理。需要注意的是,如果添加该注解的方法的返回类型是Future<>( Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作) 会开启新的线程执行任务,并阻塞主线程,执行完成后把结果返回给主线程。spring中@Async默认异步配置使用的是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,在压测情况下,会有大量写库请求进入日志写库服务,这时就会不断创建大量线程,极有可能压爆服务器内存,建议使用自定义的线程池。
@Async注解失效的几种情况
1、启动类是否开启异步服务;
2、在定义异步方法的同一个类中,调用带有@Async注解方法,该方法则无法异步执行;
3、注解的方法必须是public方法,不能是static;
4、没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器管理。