@Aysnc

2020-03-03  本文已影响0人  海生2018

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可以同时处理四个线程,那么开启三个任务线程去执行获取过程约等于顺序执行效率的三倍(当然这是理想化考虑,开启多线程还有上下文切换时间,不过针对此次使用线程池来帮我们做的,核心线程已经在应用启动时就创建好了,且程序内没有阻塞等需要争抢资源的操作)

上一篇 下一篇

猜你喜欢

热点阅读