一、基础实现
1. 开启异步调用支持
- 在启动类上添加
@EnableAsync
注解 @EnableAsync
可以配置在启动类(程序入口)或者配置类上,这里我配置在了启动类上
@SpringBootApplication
@EnableAsync // 开启异步调用
public class XxxApplication {
public static void main(String[] args) {
SpringApplication.run(XxxApplication .class, args);
}
}
2. 声明异步方法
注意:
- 在需要异步的方法上添加
@Async
注解; @Async
注解可以配置在类或者方法上,若配置在类上则说明这个类下的所有方法都将支持异步调用- 异步方法所属的类应该是spring管理的类;
@Service
public class TestServiceImpl implements ITestService {
@Override
@Async // 声明异步方法
public void testAsynTask(int i) {
LoggerFactory.getLogger(TestServiceImpl.class).info(">>>>>>>>> " + i);
}
}
3.调用异步方法
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private ITestService testService;
@ApiOperation(value = "testAsynTask", notes = "测试springBoot异步调用")
@GetMapping("/testAsynTask")
public String testAsynTask(){
for (int i = 0; i < 10; i++) {
testService.testAsynTask(i);
}
return "success";
}
}
打印结果:
二、进阶
有时候,根据需求需要制定一些多线程的策略(配置);或者需要知道线程何时结束、返回值如何获取…
1. 配置
@Configuration // 定义一个配置类
@EnableAsync // 开启异步调用
public class BeanConfig {
@Bean
public TaskExecutor taskExecutor() {
// ThreadPoolTaskExecutor 这个类是sring为我们提供的线程池类
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(3);
// 设置最大线程数
executor.setMaxPoolSize(5);
// 设置队列容量
executor.setQueueCapacity(10);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("thread_by_boo-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
2. 接收线程的返回值
有时候我们不止希望异步执行任务,还希望任务执行完成后会有一个返回值,在java中提供了Future泛型接口,用来接收任务执行结果,springboot也提供了此类支持,使用实现了ListenableFuture接口的类如AsyncResult来作为返回值的载体。比如上例中,我们希望返回一个类型为String类型的值,可以将返回值改造为:
@Async
public Future<String> testAsynTask(int i) {
log.info(">>>>>>>>> " + i);
return new AsyncResult<>("我是" + i + "wa");
}
调用:
for (int i = 0; i < 10; i++) {
// Future 的 get方法可以获得返回值
// 阻塞调用 相当于加了个锁,上一个线程不结束 下一个线程进不去
// testService.testAsynTask(i).get();
// 限时调用 超过规定的调用时间 会报超时异常
// testService.testAsynTask(i).get(1, TimeUnit.SECONDS);
Future<String> res = testService.testAsynTask(i);
}
- 监听单个线程结束
while(true){
if (res.isDone()){
System.out.println("线程结束....");
break;
}
}
- 监听多个线程结束(一)
public String testAsynTask() throws Exception {
List<Future> futureList = Lists.newArrayList(); // 声明一个线程容器
for (int i = 0; i < 10; i++) {
Future<String> res = testService.testAsynTask(i);
futureList.add(res); // 每得到一个线程就往容器中添加一个线程
}
// 监听线程是否全部结束
while (true){
if (futureList.size() < 1) {
// 当线程容器中没有任何线程的时候 说明线程已经全部结束
System.out.println("线程已经全部结束...");
break;
}
for (int i = 0; i < futureList.size(); i++) {
Future<String> future = futureList.get(i);
if (future.isDone()){
// 线程结束 则将其从线程容器中移除
System.out.println(future.get() + " >> 线程结束");
futureList.remove(i);
break;
}
}
}
return "success";
}
打印结果:
监听多个线程结束(二)
使用CountDownLatch
工具类
countDownLatch
是一个计数器,线程完成一个记录一个,计数器递减
大致流程如下
- 声明一个计数器,数量为线程的数量;
- 线程中对计数器进行递减处理,每个线程结束时计数器都减1;
- 调用CountDownLatch的await()方法,调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行;
- 程序继续向下运行…
由此,上面的代码可以改造为:
- 调用方:
List<Future<String>> futureList = Lists.newArrayList(); // 声明一个线程容器
// 声明一个计数器 数量为线程的总数量
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
Future<String> res = testService.testAsynTask(i, countDownLatch);
futureList.add(res); // 每得到一个线程就往容器中添加一个线程
}
// 阻塞 直到线程全部结束
countDownLatch.await();
log.info("线程已经全部结束....");
for (Future<String> future : futureList) {
log.info(future.get());
}
- 被调用方:
public Future<String> testAsynTask(int i,CountDownLatch countDownLatch) {
try {
log.info("我是" + i + "wa");
if (i == 2 || i == 3 || i == 4){
System.out.println(1 / 0); // 使线程异常
}
return new AsyncResult<>(i + "wa正常");
}catch(Exception e){
e.getMessage();
}finally {
// 在 finally中递减 确保每个线程结束 计数器都能递减
countDownLatch.countDown();
}
return new AsyncResult<>(i + "wa挂了");
}
打印:
类似的还有一个CyclicBarrier
的计数器,有兴趣可以了解一下
CountDownLatch
和CyclicBarrier
区别:
countDownLatch
是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次CyclicBarrier
的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用