1. 异步任务的使用场景
异步任务是相对于同步任务来讲,同步任务就是顺序执行的任务,前面的任务没有执行完,后面的任务只能等待,而异步任务不同,它可以多个任务并行执行,异步任务有很多优点:
(1)减少主流程执行时间,避免主业务被长时间阻塞,提升服务器处理请求的并量
(2)避免因为一个辅助功能的处理失败而导致整个请求失败
比如,用户下单之后,可能系统会赠送一些积分之类的,赠送积分并不是主要流程,整个过程可以放到异步任务或者消息队列中去完成,异步任务还适用于处理log、发送邮件、短信等
2. springboot异步任务如何使用
- 启动类里面使用@EnableAsync注解开启功能,自动扫描
- 定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async,需要注意以下几点:
* 要把异步任务封装到类里面,不能直接写到Controller
* 增加Future 返回结果 AsyncResult(“task执行完成”);
* 如果需要拿到结果 需要判断全部的 task.isDone() - 通过注入方式,注入到controller里面
2.1 不带返回值的异步任务
- 定义异步任务类,分别定义三个异步任务方法,方法中分别睡眠1s, 2s, 4s
package com.example.springbootdemo4.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 异步任务类,注意点:不能直接写到Controller里
*/
@Component
public class AsyncTask {
/**
* 异步任务1, @Async把该方法标记为一个异步方法,或者把方法上的注解挪到类上面也可以!
* @throws InterruptedException
*/
@Async
public void asyncTask1() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println("任务1耗时:" + (end-begin)) ;
}
@Async
public void asyncTask2() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(2000);
long end = System.currentTimeMillis();
System.out.println("任务2耗时:" + (end-begin)) ;
}
@Async
public void asyncTask3() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(4000);
long end = System.currentTimeMillis();
System.out.println("任务3耗时:" + (end-begin)) ;
}
}
- 在controller中调用上面定义的三个异步任务
package com.example.springbootdemo4.controller;
import com.example.springbootdemo4.service.AsyncTask;
import com.example.springbootdemo4.service.AsyncTask2;
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;
import java.util.concurrent.Future;
/**
* 异步任务测试
* 异步任务的意义在于把串行化执行的方法变成了异步执行,当所有异步方法执行完成后,
* 后面的方法才继续执行
*/
@RestController
@RequestMapping("/api/v1")
public class AsyncTaskController {
@Autowired
private AsyncTask task;
@Autowired
private AsyncTask2 asyncTask;
/**
* 测试异步任务 http://localhost:8080/api/v1/async
* @return
* @throws InterruptedException
*/
@GetMapping("/async")
public String execTask() throws InterruptedException {
long start = System.currentTimeMillis();
task.asyncTask1();
task.asyncTask2();
task.asyncTask3();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end -start));
return "success,suspend " + (end -start) + "ms";
}
}
- 主启动类
package com.example.springbootdemo4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
// @EnableScheduling注解开启定时任务
@EnableScheduling
// 开启异步任务:@EnableAsync会把一个类标记为异步类
@EnableAsync
public class Demo4Application {
public static void main(String[] args) {
SpringApplication.run(Demo4Application.class, args);
}
}
浏览器输入http://localhost:8080/api/v1/async进行测试,发现execTask()方法执行耗时只需要3ms左右
这时把AsyncTask类中三个方法上的@Async注解去掉,让execTask()方法中变成同步调用,测试结果如下,可见异步任务和同步任务,在执行效率上差异还是很大的!
2.2 带返回值的异步任务
- 定义一个带返回值的异步任务类AsyncTask2, @Async注解放到类上面,可以把该类的所有方法都标记为异步方法
package com.example.springbootdemo4.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* 1.异步任务类 ,不能直接写到Controller里
* 2.
*/
@Component
@Async
public class AsyncTask2 {
/**
* 异步任务1, @Async把该方法标记为一个异步方法,或者把方法上的注解挪到类上面也可以!
* @throws InterruptedException
*/
// @Async
public Future<String> task4() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(2000);
long end = System.currentTimeMillis();
System.out.println("任务1耗时:" + (end-begin)) ;
return new AsyncResult<>("任务4");
}
// @Async
public Future<String> task5() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(3000);
long end = System.currentTimeMillis();
System.out.println("任务5耗时:" + (end-begin)) ;
return new AsyncResult<>("任务5");
}
// @Async
public Future<String> task6() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println("任务6耗时:" + (end-begin)) ;
return new AsyncResult<>("任务6");
}
}
- controller中进行调用:在上面的controller中加入下面这个API接口,带返回值的异步任务,必须等到所有异步任务执行结束才能拿到结果
/**
* 带有返回值的异步任务测试
* @return
* @throws InterruptedException
*/
@GetMapping("/async2")
public String execTask2() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
Future<String> task4 = asyncTask.task4();
Future<String> task5 = asyncTask.task5();
Future<String> task6 = asyncTask.task6();
for(;;){
if(task4.isDone() && task5.isDone() && task6.isDone()){
// 等待异步任务分别执行结束
break;
}
}
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end -start));
return "success,suspend " + (end -start) + "ms" + "; task4 result = " + task4.get();
}
http://localhost:8080/api/v1/async2 测试结果