异步任务常用场景
- 发送消息
- 发送邮件
- App消息推送
- 节省运维凌晨发布任务时间提供效率
示例:
springboot启动类加上@EnableAsync
注解,异步线程任务开关
异步任务:
@Component
public class TestAsyncTask {
@Async
public Future<String> doTask1() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println("任务 1 耗时:" + (end - start) + " ms");
return new AsyncResult<>("666");
}
@Async
public Future<String> doTask2() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(800);
long end = System.currentTimeMillis();
System.out.println("任务 2 耗时:" + (end - start) + " ms");
return new AsyncResult<>("666");
}
@Async
public Future<String> doTask3() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(600);
long end = System.currentTimeMillis();
System.out.println("任务 3 耗时:" + (end - start) + " ms");
return new AsyncResult<>("666");
}
}
执行异步任务
@RestController
@RequestMapping("task")
public class DoTask {
@Autowired
private TestAsyncTask asyncTask;
@RequestMapping("testAsync")
public String execAsyncTask() throws Exception {
long start = System.currentTimeMillis();
Future<String> a = asyncTask.doTask1();
Future<String> b = asyncTask.doTask2();
Future<String> c = asyncTask.doTask3();
while (true) {
if (a.isDone() && b.isDone() && c.isDone()) {
break;
}
}
long end = System.currentTimeMillis();
String times = "任务全部完成,总耗时:" + (end - start) + " ms";
System.out.println(times);
System.out.println(a);
return times;
}
}
输出结果
异步执行用时少于同步执行
异步方法与同步方法执行过程
TestService
@Service
public class TestService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Async
public void asyncMethod() {
sleep();
logger.info("异步方法内部线程名称:{}", Thread.currentThread().getName());
}
public void syncMethod() {
sleep();
}
private void sleep() {
try {
logger.info("睡眠开始");
TimeUnit.SECONDS.sleep(2);
logger.info("睡眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TestController
@RestController
public class TestController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private TestService testService;
@GetMapping("async")
public void testAsync() {
long start = System.currentTimeMillis();
logger.info("异步方法开始");
testService.asyncMethod();
logger.info("异步方法结束");
long end = System.currentTimeMillis();
logger.info("总耗时:{} ms", end - start);
}
@GetMapping("sync")
public void testSync() {
long start = System.currentTimeMillis();
logger.info("同步方法开始");
testService.syncMethod();
logger.info("同步方法结束");
long end = System.currentTimeMillis();
logger.info("总耗时:{} ms", end - start);
}
}
访问http://localhost:8080/async
同步执行结果:
15:29:44.559 INFO 5476 --- [nio-8080-exec-1] c.e.demo.controller.TestController : 同步方法开始
15:29:44.577 INFO 5476 --- [nio-8080-exec-1] com.example.demo.service.TestService : 睡眠开始
15:29:46.579 INFO 5476 --- [nio-8080-exec-1] com.example.demo.service.TestService : 睡眠结束
15:29:46.580 INFO 5476 --- [nio-8080-exec-1] c.e.demo.controller.TestController : 同步方法结束
15:29:46.580 INFO 5476 --- [nio-8080-exec-1] c.e.demo.controller.TestController : 总耗时:2021 ms
访问http://localhost:8080/sync
,异步执行结果
15:29:58.169 INFO 5476 --- [nio-8080-exec-3] c.e.demo.controller.TestController : 异步方法开始
15:29:58.186 INFO 5476 --- [nio-8080-exec-3] c.e.demo.controller.TestController : 异步方法结束
15:29:58.187 INFO 5476 --- [nio-8080-exec-3] c.e.demo.controller.TestController : 总耗时:18 ms
15:29:58.195 INFO 5476 --- [ task-1] com.example.demo.service.TestService : 睡眠开始
15:30:00.196 INFO 5476 --- [ task-1] com.example.demo.service.TestService : 睡眠结束
15:30:00.196 INFO 5476 --- [ task-1] com.example.demo.service.TestService : 异步方法内部线程名称:task-1
弊端:每次执行异步任务都需要新建一个线程。每次线程的创建与销毁会造成不必要的开销。可使用自定义线程池
添加线程池配置类
@Configuration
public class AsyncPoolConfig {
@Bean
public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数,默认为1
executor.setCorePoolSize(20);
//线程池最大线程数。只有当核心线程都被用完并且缓冲队列满后,才会开始申超过请核心线程数的线程,默认值为Integer.MAX_VALUE
executor.setMaxPoolSize(200);
//缓冲队列
executor.setQueueCapacity(25);
//超出核心线程数的线程的空闲时候的最大存活时间
executor.setKeepAliveSeconds(200);
//线程名字前缀
executor.setThreadNamePrefix("asyncThread");
//是否等待全部线程执行完毕才关闭线程池。默认false
executor.setWaitForTasksToCompleteOnShutdown(true);
//setWaitForTasksToCompleteOnShutdown的等待时长,默认为0,不等待
executor.setAwaitTerminationSeconds(60);
//线程的处理策略 默认为abortPolicy
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
修改TestService
@async
加上线程池名称
@Async("asyncThreadPoolTaskExecutor")
再次运行http://localhost:8080/async
,结果:
15:45:56.975 INFO 1340 --- [nio-8080-exec-1] c.e.demo.controller.TestController : 异步方法开始
15:45:56.987 INFO 1340 --- [nio-8080-exec-1] c.e.demo.controller.TestController : 异步方法结束
15:45:56.988 INFO 1340 --- [nio-8080-exec-1] c.e.demo.controller.TestController : 总耗时:12 ms
15:45:57.003 INFO 1340 --- [ asyncThread1] com.example.demo.service.TestService : 睡眠开始
15:45:59.005 INFO 1340 --- [ asyncThread1] com.example.demo.service.TestService : 睡眠结束
15:45:59.005 INFO 1340 --- [ asyncThread1] com.example.demo.service.TestService : 异步方法内部线程名称:asyncThread1
异步回调
若异步方法有返回值,可使用Future接收回调值
@Async("asyncThreadPoolTaskExecutor")
public Future<String> asyncMethod() {
sleep();
logger.info("异步方法内部线程名称:{}", Thread.currentThread().getName());
return new AsyncResult<>("hello async");
}
AsyncResult为Spring实现的Future实现类
@GetMapping("async")
public String testAsync() throws Exception {
long start = System.currentTimeMillis();
logger.info("异步方法开始");
Future<String> stringFuture = testService.asyncMethod();
String result = stringFuture.get();
logger.info("异步方法返回值:{}", result);
logger.info("异步方法结束");
long end = System.currentTimeMillis();
logger.info("总耗时:{} ms", end - start);
return stringFuture.get();
}
通过future的get方法获取异步调用的返回值
通过返回结果我们可以看出Future的get方法为阻塞方法,只有当异步方法返回内容了,程序才会继续往下执行。get还有一个get(long timeout, TimeUnit unit)重载方法,我们可以通过这个重载方法设置超时时间,即异步方法在设定时间内没有返回值的话,直接抛出java.util.concurrent.TimeoutException异常。
比如设置超时时间为60秒:
String result = stringFuture.get(60, TimeUnit.SECONDS);