背景
简介
在项目中,当访问其他人的接口较慢或者做耗时任务时,不想程序一直卡在耗时任务上,想程序能够并行执行, 我们可以使用多线程来并行的处理任务,也可以使用spring提供的异步处理方式@Async。
Spring异步线程池的接口类TaskExecutor,其实质java.util.concurrent.Executor。
该接口有三个实现:
- SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方.
- ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
除这些之外,还有两个线程池实现方式:
1.ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
2.SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
异步线程池
@Async
spring3.X以后通过对@Async定义异步任务
异步的方法有3种
- 最简单的异步调用,返回值为void
- 带参数的异步调用 异步方法可以传入参数
- 异常调用返回Future
在异步处理的方法上添加注解@Async,就会启动一个新的线程去执行。
使用异步配置
SpringBoot中开启异步支持非常简单,只需要在配置类上面加上注解@EnableAsync,同时定义自己的线程池即可。 也可以不定义自己的线程池,则使用系统默认的线程池。这个注解可以放在Application启动类上,但是更推荐放在配置类上面。
源码地址
1.定义配置类初始化线程池参数
/**
* @author Wangfeng
* @date 2020/6/18 13:12
* @creed: Talk is cheap,show me the code
*/
@Configuration
@EnableAsync
public class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);//核心线程数10
executor.setMaxPoolSize(20);//最大线程数20
executor.setQueueCapacity(200);//缓冲队列200
executor.setKeepAliveSeconds(60);//允许线程的空闲时间60秒
executor.setThreadNamePrefix("taskExecutor-");//线程池名的前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
2.编写Task任务,需要注意的是要在@Async注解中指定线程池名。
@Component
public class Task {
public static Random random = new Random();
@Async("taskExecutor")
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒" + "当前线程:" + Thread.currentThread().getName());
}
@Async("taskExecutor")
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒" + "当前线程:" + Thread.currentThread().getName());
}
@Async("taskExecutor")
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒" + "当前线程:" + Thread.currentThread().getName());
}
}
3.测试验证
@RestController
@EnableAsync
public class AsyncController {
@Autowired
private Task task;
@GetMapping
@ResponseBody
@RequestMapping(value="/async")
public String test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
return "ok";
}
}
启动项目后,在浏览器访问http://localhost:8080/async 观察程序控制台打印情况,查看执行的结果。
2020-06-18 14:10:40.631 INFO 2604 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
开始做任务三
开始做任务二
开始做任务一
完成任务三,耗时:782毫秒当前线程:taskExecutor-3
完成任务一,耗时:1883毫秒当前线程:taskExecutor-1
完成任务二,耗时:6073毫秒当前线程:taskExecutor-2