应用场景:异步调用通常用在发短信、发送邮件、消息推送 、运维凌晨自动化操作等,这些场景实时性要求不高,大多都是推广统计等服务。
SpringBoot 中通过线程池来异步执行任务的两种方法:
通过 Spring 自带的 @EnableAsync 和 @Async 两个注解实现异步执行任务功能
通过自定义的方式
在通过 @EnableAsync 和 @Async 两个注解实现异步执行任务中会进一步分析 @Async 的局限性,自定义 @Async 注解的线程池,以及异常的处理。
1.只能作用于 public 方法上
2.方法不能自己调自己,也就是说不能迭代调用
在 AsyncService 中增加两个方法:一个有返回值,返回值为 Future 对象;一个没有,都通过 api 调用,具体如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
/**
* @packagb:
* @Author: fab
* @Description:
* @Date:Create:in 2019/12/4
* @Modified By:
*/
@Service
public class AsyncService {
private Logger logger = LoggerFactory.getLogger(AsyncService.class);
@Async
public void asyncData() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.toString();
}
logger.info("Execute method asynchronously, thread name = {}", Thread.currentThread().getName());
}
@Async
public Future asyncGetData() {
logger.info("Execute method asynchronously, thread name = {}", Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult("执行结果");
} catch (Exception e) {
logger.error("");
return new AsyncResult(null);
}
}
@Async("selfConfigThreadPool")
public void asyncSelfData() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.toString();
}
logger.info("Execute method asynchronously, thread name = {}", Thread.currentThread().getName());
}
}
AsyncController 如下:
import com.hikvision.ga.common.BaseResult;
import com.hikvision.zl.modules.service.AsyncService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @packagb:
* @Author: fab
* @Description:
* @Date:Create:in 2019/12/4
* @Modified By:
*/
@Api
@RestController
@RequestMapping(value = "/api/async")
public class AsyncController {
@Autowired
private AsyncService dbService;
/**
* 请求,立即返回,但是不是具体的执行结果,具体的任务在线程池中慢慢的执行
*/
@GetMapping("/asyncData")
public void asyncData() {
dbService.asyncData();
}
/**
* 请求,执行完毕后再返回具体的结果,具体的任务在线程池中执行
*/
@GetMapping("/asyncGetData")
public void asyncGetData() throws Exception {
System.out.println(dbService.asyncGetData().get());
}
@GetMapping("/asyncSelfData")
public BaseResult asyncSelfData(){
dbService.asyncSelfData();
return BaseResult.success(null);
}
}
测试
http://127.0.0.1:9001/zlTestDemo/api/async/asyncData
对于 /api/async/asyncData 请求,立即返回,但是不是具体的执行结果,具体的任务在线程池中慢慢的执行,具体如下:
对于 /api/async/asyncGetData 请求,执行完毕后再返回具体的结果,具体的任务在线程池中执行,具体如下
从执行结果来看,AsyncService 中的 asyncData 方法和 asyncGetData 方法都执行在 SimpleAsyncTaskExecutor 线程池中
http://127.0.0.1:9001/zlTestDemo/api/async/asyncData
http://127.0.0.1:9001/zlTestDemo/api/async/asyncGetData
http://127.0.0.1:9001/zlTestDemo/api/async/asyncSelfData
新增配置类 ThreadConfig,通过 @Bean 来配置单个或者多个线程池。
ThreadConfig 配置类,定义了两个线程池,一个用来发送邮件,一个用来处理心跳服务
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @packagb:
* @Author: fab
* @Description:
* @Date:Create:in 2019/12/4
* @Modified By:
*/
@Configuration
public class ThreadConfig {
/**
* 邮件服务
* @return
*/
@Bean("sendMailExecutorService")
public ExecutorService sendMailExecutorService() {
return new ThreadPoolExecutor(2, 2,
60L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
}
/**
* 心跳服务
* @return
*/
@Bean("heartbeatExecutorService")
public ExecutorService heartbeatExecutorService() {
return new ThreadPoolExecutor(1, 1,
60L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
}
}
通过 @Qualifier("sendMailExecutorService") 和 @Autowired 注入
package com.hikvision.zl.modules.service;
ThreadService
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* @packagb:com.hikvision.zl.modules.service
* @Author: fab
* @Description:
* @Date:Create:in 2019/12/4
* @Modified By:
*/
@Service
public class ThreadService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Qualifier("sendMailExecutorService")
@Autowired
private ExecutorService sendMailExecutorService;
@Qualifier("heartbeatExecutorService")
@Autowired
private ExecutorService heartbeatExecutorService;
public void heartbeatService() {
heartbeatExecutorService.submit(() -> {
// TODO 负责心跳有关的工作
logger.info("Execute heartbeatService asynchronously, thread name = {}", Thread.currentThread().getName());
});
}
public Future sendMailService() {
return sendMailExecutorService.submit(() -> {
logger.info("Execute sendMailService asynchronously, thread name = {}", Thread.currentThread().getName());
// 休息1秒,模拟邮件发送过程
TimeUnit.SECONDS.sleep(1);
return true;
});
}
}
注意 ThreadConfig 中配置了多个线程池,由于 ExecutorService 类型相同,因此需要加上 Bean 的名称以及在注入的时候需要加上 @Qualifier
通过 ThreadController 调用服务,具体如下
ThreadController 中 /api/asyncThread/heartbeat api 执行不需要返回,/api/asyncThread/sendMail api 需要返回结果
import com.hikvision.ga.common.BaseResult;
import com.hikvision.zl.modules.service.ThreadService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @packagb:
* @Author: fab
* @Description:
* @Date:Create:in 2019/12/4
* @Modified By:
*/
@Api
@RestController
@RequestMapping(value = "/api/asyncThread")
public class ThreadController {
@Autowired
private ThreadService threadService;
/**
* 请求,立即返回,但是不是具体的执行结果,具体的任务在线程池中慢慢的执行
*/
@GetMapping("/heartbeat")
public BaseResult asyncData() {
threadService.heartbeatService();
return BaseResult.success(null);
}
/**
* 请求,执行完毕后再返回具体的结果,具体的任务在线程池中执行
*/
@GetMapping("/sendMail")
public BaseResult asyncGetData() throws Exception {
return BaseResult.success(threadService.sendMailService().get());
}
}
测试
http://127.0.0.1:9001/zlTestDemo/api/asyncThread/heartbeat
http://127.0.0.1:9001/zlTestDemo/api/asyncThread/sendMail