SpringBoot异步方法-@Async

2021-01-23  本文已影响0人  小三哥_e3f4

1、我们为什么要使用异步方法

异步调用通常用在发短信、发送邮件、消息推送 、运维凌晨自动化操作等,这些场景实时性要求不高,大多都是推广统计等服务。
我们采用异步的方式来处理这样耗时实时性要求不高的请求,工作线程可以让后台线程来接手,自己可以及时地被释放到线程池中用于进行后续请求的处理,从而提高了整个服务器的吞吐能力。

springboot中如何实现异步方法

步骤一: SpringApplication启动类上添加@EnableAsync注解
@EnableAsync
@SpringBootApplication
public class SystemApplication{
    public static void main(String[] args) {
        SpringApplication.run(MicStadiumSystemApplication.class, args);
    }
}
步骤二: 配置线程池

注:

/**
 * 线程池配置
 */
@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
    // 核心线程数为服务器的cpu核心数
    private static int corePoolSize = Runtime.getRuntime().availableProcessors();
    // 线程池中允许的最大线程数
    private static int maxPoolSize = 2 * corePoolSize + 1;
    // 工作队列大小
    private static int queueCapacity = 5000;

    @Override
    public Executor getAsyncExecutor() {
        log.info("核心线程数: "+ corePoolSize + " 最大线程数: " + maxPoolSize);
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数
        executor.setCorePoolSize(corePoolSize);
        //最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //队列大小
        executor.setQueueCapacity(queueCapacity);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setThreadNamePrefix("QueryHealthKit-");
        // 拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

ThreadPoolTaskExecutor属性解释。

1、poolSize<corePoolSize:直接创建新的线程处理新来的任务。
2、poolSize>=corePoolSize && 任务队列未满时:新来的任务加入任务队列。
3、poolSize>corePoolSize && 任务队列满了时:
poolSize<maxPoolSize创建新的线程处理新来的任务。
poolSize=maxPoolSize执行拒绝策略。

步骤三: 创建异步方法并添加@Async注解
@Service
public class AsyncQueryTask {
    // 用与返回接口
    Map<String, Object> map = new HashMap<>();
    /**
     * 异步方法
     */
    @Async
    public Future<Object> asyncQuery() {
         // 异步方法
         ......
        return new AsyncResult<>(map);
    }
}

注意事项:以下内容需要严格遵守

  • 异步不能方法使用static修饰
  • 异步类需要使用@Component注解(或其他注解)
  • 异步方法不能与它的调用方法在同一个类中
  • 在Async方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
  • 异步方法返回值必须是void或Future。
步骤四: 创建调用者方法

如果Async方法的返回值是void,则调用者方法与普通方法无异。
如果Async方法的返回值是Future,以下代码展示对结果的收集。

    @Override
    public void healthKitJob() {
        Date startDate = new Date();
        log.info("任务开始时间{}", startDate);
      
        // 保存异步任务列表
        List<Future<Object> futureList = Collections.synchronizedList(Lists.newArrayList());

        // ......
        for (int i = 0; j < 1000; i++) {
            // 1.批量执行异步操作
            Future<Object> asyncResult = asyncQueryTask.asyncQuery();
            // 2.加入任务列表
            futureList.add(asyncResult);
        }

                // 3.结果归集,用迭代器遍历futureList,高速轮询
                while (futureList.size() > 0) {
                    Iterator<Future<Object> ite = futureList.iterator();
                    //遍历一遍
                    while (ite.hasNext()) {
                        Future<Object> ft = ite.next();
                        //如果任务完成取结果,否则判断下一个任务是否完成
                        if (ft.isDone()) { // 判断是否执行完毕
                            try {
                                Object ftObject = ft.get(); // 获取结果
                                // 收集返回结果
                                // ......
                            } catch (Exception e) {
                                e.printStackTrace();
                            } finally {
                                ite.remove();  // 任务完成移除任务
                            }
                        } else {
                            try {
                                // 避免CPU高速运转,这里休息2毫秒,CPU纳秒级
                                Thread.sleep(2);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                // 4.处理归集后的结果

                Date endDate = new Date();
                log.info("任务结束时间{},用时{}", endDate, endDate.getTime() - startDate.getTime());
    }

注:结果归集要等到所有异步方法都返回后才能往下处理。

执行效果

同时有多个线程在处理任务。


image.png
上一篇 下一篇

猜你喜欢

热点阅读