CompletableFuture实现简易版任务超时重试设计

概要

众所周知,CompletableFuture随着Java8的诞生粉墨登场,这个类提供了非常强大的Future的扩展功能,可以大大简化我们进行异步编程时的复杂性以及代码的繁琐性,而且还是函数式的编码方式,可以通过各种静态方法对多个任务进行转换、计算以及组合最终收集;当然也可以进行任务的超时重试,这样就避开了对spring-retry以及其他框架的依赖引入,既可以用来学习领悟,也能进行简单的任务超时重试处理。

前置知识

  • CompletableFuture,本站有很多优秀文章,如下推荐一篇很喜欢的

    CompletableFuture使用详解

  • 线程池基础知识,以及对定时任务类型线程池ThreadPoolTaskExecutor基本了解,当然本文也会进行初步入门介绍

话不多说,下面是快速开始;show my code

引入

由于是写的demo版本,仅需要引入Spring基础框架以及要求Jvm环境在1.8以上即可;
如有疑问或者错误,请大方指教,万分感谢

超时任务重试代码

/**
 * @author zhangsx
 * @description 简易的超时重试任务设计
 * @date 2024-03-19 15:41:04
 */
public class LayZi {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        ThreadPoolTaskExecutor taskExecutor = createExecutor();
        taskExecutor.initialize();
        //超时时间2s
        long timeout = 2000L;
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("===============业务代码开始执行=================");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //=========================模拟超时异常==========
            int a = 1 / 0;
            return "业务代码执行完了";
        }, taskExecutor);

        completableFuture.applyToEither(timeOut(threadPoolExecutor, timeout, TimeUnit.MILLISECONDS), Function.identity())
                .exceptionally(throwable -> {
   	                //TODO 此处可以对超时特殊异常类型进行判断处理
                    System.out.println("发生异常: " + throwable.getMessage());
                    System.out.println("在这里重新执行业务代码吧哈哈哈哈");
                    return "超时了啊";
                });
        String s = null;
        try {
            s = completableFuture.get();
        } catch (Exception e) {
            System.out.println("超时异常:" + e.getMessage());
        }
        System.out.println(s);
    }



    public static <T> CompletableFuture<T> timeOut(ScheduledThreadPoolExecutor threadPoolExecutor, long timeout, TimeUnit unit) {
        CompletableFuture<T> result = new CompletableFuture<T>();
        threadPoolExecutor.schedule(
                () -> {
                    System.out.println("========计时任务来同步计时一下==========");
                    result.completeExceptionally(new Exception("超时了啊啊啊啊啊"));
                },
                timeout,
                unit);
        return result;
    }


    public static ThreadPoolTaskExecutor createExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(8);
        // 设置队列容量
        executor.setQueueCapacity(3);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(5);
        // 设置默认线程名称
        executor.setThreadNamePrefix("test-");
        // 设置拒绝策略: 线程池满后阻断请求
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }

}

实现思路

  • 使用CompletableFutureapplyToEither()方法来对两个任务进行聚合,参考方法注释

    • applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值

    • acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值

    • runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)

  • ScheduledThreadPoolExecutor带有定时器的线程池执行器,它继承了ThreadPoolExecutor,底层使用了延迟队列来对任务进行定时处理,如下是基本的常用方法

    • schedule(Runnable command, long delay, TimeUnit unit) // 无返回值的延迟任务

    • schedule(Callable callable, long delay, TimeUnit unit) // 有返回值的延迟任务

    • scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit) // 固定频率周期任务

    • scheduleWithFixedDelay(Runnable command,long initialDelay, long delay, TimeUnit unit) // 固定延迟周期任务

    本次使用了基本的无返回值延迟任务,也就是空的任务执行,超时后使用完成任务异常方法来抛出指定异常,这样applyToEither来组合原本的业务时,可以对超时特殊异常进行二次执行处理。如果没有超时,则最后使用future来get获取业务执行的结果。

后续优化

优化方向
  • ScheduledThreadPoolExecutor定时任务线程池设置为后台守护线程,项目可以进行公用

  • 尝试对exceptionally进行判断处理,增加重试次数参数,满足多次任务重试

其他解释

  • CompletableFuture中指定线程池参数,也就是demo中的taskExecutor参数的目的是自定义线程池,避免了使用CompletableFuture实现中默认的线程池**ForkJoinPool.commonPool()**,使用默认的缺点就是,全局唯一,业务公用,不好维护,也容易相互影响,强烈推荐使用自定义线程池;
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值