定时任务规范开发演变-函数式接口+设计模式

前言

一个理论:java 中的线程不被允许被销毁

一个设想:函数式语言中怎么使用设计模式

定时任务处理大数据量数据编码逻辑

定时任务简单处理逻辑

@RestController()
@RequestMapping(value ="task" )
public class TaskController {

    @Autowired
    ITaskService taskService;

    @RequestMapping(value = "/baseTask",method = RequestMethod.GET)
    public void baseTask() {
        baseTask();
    }
    @Override
    public void baseTask() {
        int pageSize = 1000;
        Long index = 0L;
        List<Long> dataList = List.of();
        for (Integer pageNum = 1; pageNum <= 300; pageNum++) {
            try {
                dataList = getBaseList(index, pageSize);
            } catch (Exception e) {
                log.error("getBaseList  error", e);
            }
            if (CollectionUtils.isEmpty(dataList)) {
                break;
            }
            baseTaskList(dataList);
            //添加定时任务游标
            index = dataList.get(dataList.size() - 1);
            log.info("定时任务 {}  当前页数 {} 当前页数 {} 推送当前游标 {}", "baseTask", pageNum, dataList.size(), index);
        }
    }

    private void baseTaskList(List<Long> dataList) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        ExecutorService es = ExecutorServiceUtil.init(8, 8, 1000000, TimeUnit.MILLISECONDS, "baseTask");
        for (Long customerId : dataList) {
            es.submit(() -> {
                try {
                    RespObj<Long> respObj = baseTaskData(customerId);
                    log.info("baseTaskData customerId {} 推送结果 success {} respObj {}", customerId, respObj.getSuccess(), respObj.getMsg());
                } catch (Exception e) {
                    log.debug("baseTaskData 异常 customerId {}", customerId, e);
                }
            });
        }
        ExecutorServiceUtil.stop(es);
    }
    
    private RespObj<Long> baseTaskData(Long customerId) {
        RespObj<Long> respObj = new RespObj(customerId, "推送成功");
        if (customerId % 10 == 1 || customerId % 10 == 2) {
            respObj.setSuccess(false);
            respObj.setMsg("错误类型1 尾号为1,2");
            return respObj;
        }
        return respObj;
    }
    
}

线程池工具类第一版

@Slf4j
public class ExecutorServiceUtil {

    public static final int TIMEOUT = 60;

    public static ExecutorService init(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, String threadPoolName) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(threadPoolName).setUncaughtExceptionHandler((thread, throwable)-> {log.info("ThreadPool {} got exception",thread,throwable);}).build();
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                keepAliveTime, unit,
                new LinkedBlockingQueue<>(), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    }

    public static void stop(ExecutorService es) {
        //执行后不再接收新任务,如果里面有任务,就执行完
        es.shutdown();
        try {
            //通常shutdown之后调用awaitTermination,作用是:后者会阻塞当前线程,等待剩余任务执行完,然后继续往下执行。如果不适用await,那么shutdown之后,很可能导致剩余任务得不到执行(整个程序退出),或是执行出现异常(某些资源被释放之类)。
            if (!es.awaitTermination(TIMEOUT, TimeUnit.MINUTES)) {
                //执行后不再接受新任务,如果有等待任务,移出队列;有正在执行的,尝试停止 超时的时候向线程池中所有的线程发出中断(interrupted)。
                es.shutdownNow();
            }
        } catch (InterruptedException ignore) {
            es.shutdownNow();
        }
    }

}

此程序可以实现每次对 1000 数据进行批量并发处理,但是缺点很多。

  • 缺点 1:每次处理 1000 数据就会创建线程池,因为 shutdown()方法一旦执行,该线程池就不可用。
  • 缺点 2:通用处理逻辑太复杂,要求太多,目前也没有对日志进行统一处理。

优化线程池 CompletableFuture 函数式编程设计模式

定时任务处理逻辑函数式

@RestController()
@RequestMapping(value ="task" )
public class TaskController {
    @RequestMapping(value = "/taskTemplate",method = RequestMethod.GET)
    public Map<String, Long> taskTemplate() {
        return taskService.taskTemplate();
    }
    @Override
    public Map<String, Long> taskTemplate() {
        ExecutorServiceTaskUtil execute = ExecutorServiceTaskUtil.execute("taskTemplate", 4,
                (ExecutorServiceTaskUtil executorServiceTaskUtil) -> {
                    int pageSize = 1000;
                    Long index = 0L;
                    List<Long> dataList = List.of();
                    for (Integer pageNum = 1; pageNum <= 100; pageNum++) {
                        try {
                            dataList = getBaseList(index, pageSize);
                        } catch (Exception e) {
                            log.error("taskTemplate getBaseList  error", e);
                        }
                        if (CollectionUtils.isEmpty(dataList)) {
                            break;
                        }
                        executorServiceTaskUtil.taskList(dataList.stream().map(
                                (cid) -> executorServiceTaskUtil.taskData(() -> taskData(cid), cid)).collect(Collectors.toList()));
                        //添加定时任务游标
                        index = dataList.get(dataList.size() - 1);
                        log.info("定时任务 {}  当前页数 {} 当前页数 {} 推送当前游标 {} listResult {}", "taskTemplate", pageNum,
                                dataList.size(), index, JSONUtil.toJsonStr(executorServiceTaskUtil.getTaskListCountMap()));
                    }
                });
        return execute.getTaskCountMap();
    }

    private TaskResultResponse taskData(Long customerId) {
        TaskResultResponse<Long> result = new TaskResultResponse(customerId, true);
        if (customerId % 10 == 1 || customerId % 10 == 2) {
            result.setMsg("错误类型1 尾号为1,2");
            return result;
        }
        return result;
    };
    
}

定时任务统一返回值

@Data
    public class TaskResultResponse<T> {
        private T data;
        private Boolean success;
        private String msg;

        public TaskResultResponse(T t, Boolean success) {
            this.success = success;
            if (success) {
                this.msg = "执行成功";
            } else {
                this.msg = "执行失败";
            }
            this.data = t;
        }
    }

定时任务工具类第二版


@Slf4j
@Data
public class ExecutorServiceTaskUtil {

    //核心线程数
    private Integer poolSize;
    //线程名
    private String threadPoolName;
    //线程池
    private ThreadPoolExecutor taskExecutor;
    //任务情况统计
    Map<String, Long> taskCountMap = Maps.newConcurrentMap();
    //任务列表统计
    Map<String, Long> taskListCountMap = Maps.newConcurrentMap();
    //任务总数
    private Long taskCount = 0L;
    private Long successCount = 0L;
    private Long failCount = 0L;
    //开始时间
    private Long startTime = 0L;
    //结束时间
    private Long endTime = 0L;

    public ExecutorServiceTaskUtil(Integer poolSize, String threadPoolName) {
        this.poolSize = poolSize;
        this.threadPoolName = threadPoolName;
        //定义线程池名 和 线程 抛出异常处理机制
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("timedTask-" +threadPoolName + "-%d").
                setUncaughtExceptionHandler((thread, throwable)-> log.error("ThreadPool {} error",thread,throwable)).build();
        //定义全为核心线程,因为没有救急线程  long keepAliveTime, TimeUnit unit, 参数无意义
        //救急线程在定时的线程池的队列满的时候且核心线程都在执行时 才会创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize,
                1000L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
        this.taskExecutor = executor;
        this.startTime = System.currentTimeMillis();
    }

    //Consumer 接口 做了处理器
    public static ExecutorServiceTaskUtil execute(String taskName,int poolSize,Consumer<ExecutorServiceTaskUtil> consumer){
        ExecutorServiceTaskUtil executorServiceTaskUtil = new ExecutorServiceTaskUtil(poolSize,taskName);
        try {
            consumer.accept(executorServiceTaskUtil);
        } finally {
            executorServiceTaskUtil.stop();
        }
        return executorServiceTaskUtil;
    }

    //supplier接口 做了包装器
    public  Supplier taskData(Supplier<TaskResultResponse>  supplier, Object resultData){
        return   () -> {
            TaskResultResponse<Object> result = new TaskResultResponse<>(resultData, true);
            try {
                result = supplier.get();
                log.info("{} taskData  resultData {} result {}", threadPoolName, resultData, JSONUtil.toJsonStr(result));
            } catch (Exception e) {
                log.error("{} taskData 异常 resultData {}",threadPoolName, resultData, e);
                result.setMsg("执行异常");
                result.setSuccess(false);
            }
            return result;
        };
    }
    
    //supplier接口 做了执行器
    public void taskList(List<Supplier> dataSupplierList) {
        if(dataSupplierList == null || dataSupplierList.size() == 0){
            return;
        }
        int size = dataSupplierList.size();
        long startTime = System.currentTimeMillis();
        CompletableFuture<TaskResultResponse<Object>>[] callableArray = new CompletableFuture[size];
        for (int i = 0; i < size; i++) {
            callableArray[i] = CompletableFuture.supplyAsync(dataSupplierList.get(i), taskExecutor);
        }
        try {
            CompletableFuture.allOf(callableArray);
        } catch (Exception e) {
            log.error("{} CompletableFuture allOf error",threadPoolName ,e);
        }
        List<CompletableFuture<TaskResultResponse<Object>>> callableList = Lists.newArrayList(callableArray);
        this.taskListCountMap = callableList.stream()
                .collect(Collectors.groupingBy(e -> {
                    try {
                        return e.get().getMsg();
                    } catch (Exception ex) {
                        return "异步执行失败";
                    }
                }, Collectors.counting()));

        Map<Boolean, Long> collect = callableList.stream()
                .collect(Collectors.groupingBy(e -> {
                    try {
                        return e.get().getSuccess();
                    } catch (Exception ex) {
                        return false;
                    }
                }, Collectors.counting()));

        addListCount();
        Long  falseListCount = collect.getOrDefault(false, 0L);
        Long  successListCount = collect.getOrDefault(true, 0L);
        taskCount +=size;
        successCount += successListCount;
        failCount += falseListCount;
        log.info("{} taskList time {}s" , threadPoolName , (System.currentTimeMillis()  - startTime)/1000 );
    }

    private void addListCount(){
        if(taskListCountMap == null ||taskListCountMap.size() == 0){
            return ;
        }
        for (String key : taskListCountMap.keySet()) {
            Long oldValue = taskCountMap.get(key);
            // 键不存在,将键值对添加到 ConcurrentHashMap 中
            if (oldValue == null) {
                taskCountMap.putIfAbsent(key, 0L);
                oldValue = 0L;
            }
            Long value = taskListCountMap.get(key);
            // 键存在,将键对应的值自增 1
            while (true) {
                Long newValue = oldValue + value;
                if (taskCountMap.replace(key, oldValue, newValue)) {
                    // 替换成功,退出循环
                    break;
                } else {
                    // 替换失败,重新获取最新值并重试
                    oldValue = taskCountMap.get(key);
                }
            }
        }
    }

    public  void stop() {
        //执行后不再接收新任务,如果里面有任务,就执行完
        taskExecutor.shutdown();
        try {
            //通常shutdown之后调用awaitTermination,作用是:后者会阻塞当前线程,等待剩余任务执行完,然后继续往下执行。如果不适用await,那么shutdown之后,很可能导致剩余任务得不到执行(整个程序退出),或是执行出现异常(某些资源被释放之类)。
            if (!taskExecutor.awaitTermination(1000, TimeUnit.MINUTES)) {
                //执行后不再接受新任务,如果有等待任务,移出队列;有正在执行的,尝试停止 超时的时候向线程池中所有的线程发出中断(interrupted)。
                taskExecutor.shutdownNow();
            }
        } catch (InterruptedException ignore) {
            taskExecutor.shutdownNow();
        }
        this.endTime = System.currentTimeMillis();
        log.info("{} time {}s" , threadPoolName , (startTime  - endTime)/1000 );
    }

}

使用了 CompletableFutureApi 处理线程任务,通过函数式接口处理通用代码,通用日志。

  • 优点 1:创建一次线程池,可重复使用此线程池
  • 优点 2:通用处理逻辑简化,不需要知道工具类中的函数时接口中的定义和逻辑,只需要使用即可,把对应参数传进去工具类进行日志打印和参数的传递

代码解读

为什么要这样写代码?

ExecutorServiceTaskUtil execute = ExecutorServiceTaskUtil.execute("taskTemplate", 4,
            (ExecutorServiceTaskUtil executorServiceTaskUtil) -> {
                //定时任务处理逻辑
                executorServiceTaskUtil.taskList(
                        dataList.stream().map((cid) -> executorServiceTaskUtil.taskData(
                                () -> 单个数据处理逻辑, cid)
                        ).collect(Collectors.toList()));
            })

定时任务工具类在代码中的集成为以上逻辑

  • 定时任务执行: public static ExecutorServiceTaskUtil execute(String taskName,int poolSize,Consumer<ExecutorServiceTaskUtil> consumer)
    • 不希望使用者定义太多线程池的参数,只需定义线程池的名字和线程数。
    • 封闭对线程池的操作。
    • 其次在定时任务处理逻辑中,会用到线程池工具类对象 通过 interface Consumer<T> 参数接口 进行初始化线程池并且传递到接口的实现方法中,实现对于线程池的初始化和销毁。
      • 装饰器模式
      • 处理器模式
  • 数据集合处理 public void taskList(List<Supplier> dataSupplierList) 目的进行任务处理,数据统计。
    • CompletableFuture.supplyAsync(dataSupplierList.get(i), taskExecutor) 用于线程任务处理
    • CompletableFuture.allOf(callableArray); 用于主线程阻塞
    • 进行数据统计
  • 数据处理 public Supplier taskData(Supplier<TaskResultResponse> supplier, Object resultData)
    • 进行日志打印
    • 必须返回 TaskResultResponse 对象,目的用于数据统计。
    • 通过 interface Supplier<T> 参数接口 进行方法传递。
      • 静态代理模式
      • 装饰器模式

展望:之后的函数式接口结合不同的设计模式处理接口

救急线程

注:实验了使用救急线程的情况,救急线程只能在核心线程都在处理中且线程队列满的时候创建 所以不使用救急线程满足需求

ThreadPoolExecutorWrap executor = new ThreadPoolExecutorWrap(0, 10, 1000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), "timedTask-"+threadPoolName+"-");

处理 1000 条数时由于线程队列不满所以创建了一个核心线程处理数据,未创建救急线程
救急线程监控情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值