@Aysnc
Spring Boot @Async
@Async注解包含在Web依赖中
本质上就是Springboot帮你托管了线程池
开启异步线程
在SpringBoot的启动类上面加上@EnableAsync
例如
@EnableAsync
@SpringBootApplication
public class Application {
public static void main(String[] args) {
......
}
线程池的配置
虽然SpringBoot已经帮我们做了默认配置,但是为了要掌握具体的内容还是可以自定义配置
编写配置类
配置类需要实现AsyncConfigurer
的一些方法,根据需要来实现
例如下面的是自定义线程池的属性
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
//设置核心线程数
threadPool.setCorePoolSize(4);
//设置最大线程数,最大线程数是当缓冲队列已经塞满了的时候,才会创建活动线程,直至上限
threadPool.setMaxPoolSize(8);
//线程池所使用的缓冲队列
threadPool.setQueueCapacity(4);
// 当应用关闭的时候,是否等待异步任务完成再关闭其他Bean
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间 (默认为0,此时立即停止),等待xx秒后强制停止
threadPool.setAwaitTerminationSeconds(15);
// 当启动了除核心线程之外的线程,会保持空闲60s后销毁
threadPool.setKeepAliveSeconds(60);
// 线程名称前缀
threadPool.setThreadNamePrefix("xxx-Async-");
// 当线程池没有能力处理,也就是已达最大活动线程及最大缓冲时的拒绝策略
threadPool.setRejectedExecutionHandler(new CallerRunsPolicy());
// 初始化线程
threadPool.initialize();
return threadPool;
}
}
定义任务
定义任务只需要在方法上加上注解@Async
即可,要注意的是它的类一定要注册到Spring中,也就是加入了@Component
注解
例如
@Component
class Task{
@Async
public Future<Integer> execute() {
...
}
}
异步调用
很多其他Blog会讲一下同步调用,这其实是和@Async
没什么太大关系,所谓同步,就是主线程会等待任务线程完成后,在进行下一步的操作。既然是这样的应用场景,为什么还要用多线程呢?如果存在依赖关系的话,为什么不直接调用方法呢?
所以例如在取多个不相关结果,并要获取归并结果时,可以用这样的异步调用(这里的异步调用指的是回调模式,主线程非阻塞)
同步IO是用户进程主动发起(等待系统内核进行IO)
异步IO是系统内核主动发起(回调给用户进程)
任务的返回值
Future<T>
对象是并发包中的统一的异步处理结果,例如在本次使用的结果,用SpringBoot的AsyncResult<T>
来返回。
例如return new AsyncResult<>(1)
代表返回一个整数1
接收返回值(回调)
一般用于接收返回值是在调用异步任务的线程中
例如
public void executeTask() {
long startTime = System.currentTimeMillis();
List<Future<Integer>> taskList=new ArrayList<>();
taskList.add(task.execute());
...
// 不断轮询任务是否完成
while (taskList.size() > 0) {
Iterator<Future<Integer>> it = taskList.iterator();
while (it.hasNext()) {
Future<Integer> task = it.next();
if (!task.isCancelled() && task.isDone()) {
try {
// 简单的将结果累加,可以根据结果做任意事情
Integer total = task.get();
// get()是一个阻塞性的方法,但是我们会提前检查他是不是完成了,所以这里是立即获得结果
sum += total;
} catch (InterruptedException e) {
log.error("task thread interrupted : {}", e.getMessage());
} catch (ExecutionException e) {
log.error("task execute error : {}", e.getMessage());
}
it.remove();
}
}
}
long finishTime = System.currentTimeMillis();
log.info("Process finished , time used : {} ms",
finishTime - endTime);
}
但需要注意的是,这样做在结果归并的时候其实还是顺序执行的,只不过不是有序的顺序。不过这样并行化的方式主要加快了获取结果的过程,例如4核心CPU可以同时处理四个线程,那么开启三个任务线程去执行获取过程约等于顺序执行效率的三倍(当然这是理想化考虑,开启多线程还有上下文切换时间,不过针对此次使用线程池来帮我们做的,核心线程已经在应用启动时就创建好了,且程序内没有阻塞等需要争抢资源的操作)