按数据量和期望时间自动生成线程池并执行任务

引例

最近帮以前的老东家,现在的兼职雇主做一些优化,比较多的是性能这一块。今天心血来潮想着每次自己计算线程池的线程数量太麻烦了,要是能有个办法能自动计算线程数量,创建线程池,并执行任务那就方便了。

代码

先上代码,附上贴心测试哟~

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * @author siyang
 * @date 2024/4/24 19:22
 * @description 简单异步执行器
 */
@Slf4j
public class SimpleExecutor {
    public static void execute(Runnable runnable) {
        new Thread(runnable).start();
    }

    /**
     * 通过待消费数据,期望执行时间,每次任务预测执行时间来自动创建线程池并执行任务
     *
     * @param list 需要消费的数据
     * @param expireCostTime 期望执行时间, 格式:1ms、1s、1min、1h
     * @param forecastTimePreTask 每次任务预计执行时间, 格式同上
     * @param consumer 任务执行器
     * @param errorHandler 错误处理器
     */
    public static <T> void executeWithForecast(
            List<T> list,
            String expireCostTime,
            String forecastTimePreTask,
            Consumer<T> consumer,
            BiConsumer<T, Exception> errorHandler) {
        long start = System.currentTimeMillis();
        int taskNum = list.size();
        CountDownLatch latch = new CountDownLatch(taskNum);
        ExecutorService executor = getExecutor(taskNum, expireCostTime, forecastTimePreTask);
        AtomicLong minTimeCost = new AtomicLong(1000L);
        AtomicLong maxTimeCost = new AtomicLong(0L);
        list.forEach(
                item ->
                        executor.execute(
                                () -> {
                                    try {
                                        long t1 = System.currentTimeMillis();
                                        consumer.accept(item);
                                        long t2 = System.currentTimeMillis();
                                        long timeCost = t2 - t1;
                                        minTimeCost.updateAndGet(t -> Math.min(t, timeCost));
                                        maxTimeCost.updateAndGet(t -> Math.max(t, timeCost));
                                    } catch (Exception e) {
                                        if (Objects.nonNull(errorHandler)) {
                                            errorHandler.accept(item, e);
                                        }
                                    } finally {
                                        latch.countDown();
                                    }
                                }));
        try {
            latch.await();
            long end = System.currentTimeMillis();
            long cost = end - start;
            long diff = cost - parseTime(expireCostTime);
            log.info(
                    "【SimpleExecutor.executeWithForecast】执行记录 \n ===> 执行任务数: {};  期望时间:{};  实际耗时 {}ms; 比预期{} {}ms \n ===> 单次任务最小耗时 {}ms; 最大耗时 {}ms",
                    list.size(),
                    expireCostTime,
                    cost,
                    (diff > 0 ? "多" : "少"),
                    Math.abs(diff),
                    minTimeCost.get(),
                    maxTimeCost.get());
        } catch (InterruptedException ignore) {
        } finally {
            executor.shutdown();
        }
    }

    /**
     * 通过总任务量,期望执行时间,每次任务预测执行时间来获得一个线程池
     *
     * @param taskNum 总任务数
     * @param expireCostTime 期望执行时间
     * @param forecastTimePreTask 每次任务预计执行时间
     * @return 线程池
     */
    private static ExecutorService getExecutor(
            int taskNum, String expireCostTime, String forecastTimePreTask) {
        if (taskNum <= 0) {
            // 仍旧返回一个单线程,避免可能的NPE
            return Executors.newSingleThreadExecutor();
        }
        // 计算线程数量
        int roundSize =
                BigDecimal.valueOf(taskNum)
                        .divide(
                                BigDecimal.valueOf(parseTime(expireCostTime))
                                        .divide(
                                                BigDecimal.valueOf(parseTime(forecastTimePreTask)),
                                                4,
                                                RoundingMode.HALF_UP),
                                0,
                                RoundingMode.HALF_UP)
                        .intValue();
        int executorSize = Math.max(roundSize, 1);
        log.info("taskNum={}; 线程数={}", taskNum, executorSize);
        return Executors.newFixedThreadPool(executorSize);
    }

    private static long parseTime(String timeStrWithUnit) {
        if (timeStrWithUnit.endsWith("ms")) {
            String millisecond = timeStrWithUnit.substring(0, timeStrWithUnit.indexOf("m"));
            return Long.parseLong(millisecond);
        } else if (timeStrWithUnit.endsWith("s")) {
            String second = timeStrWithUnit.substring(0, timeStrWithUnit.indexOf("s"));
            return Long.parseLong(second) * 1000;
        } else if (timeStrWithUnit.endsWith("min")) {
            String minute = timeStrWithUnit.substring(0, timeStrWithUnit.indexOf("m"));
            return Long.parseLong(minute) * 60 * 1000;
        } else if (timeStrWithUnit.endsWith("h")) {
            String hour = timeStrWithUnit.substring(0, timeStrWithUnit.indexOf("h"));
            return Long.parseLong(hour) * 60 * 60 * 1000;
        }
        return 0;
    }

    public static void main(String[] args) {
        System.out.println(parseTime("1ms"));
        System.out.println(parseTime("1s"));
        System.out.println(parseTime("1min"));
        System.out.println(parseTime("1h"));

        int taskNum = 20;
        String expireCostTime = "1s";
        String forecastTimePreTask = "300ms";

        List<String> taskList = new ArrayList<>();
        for (int i = 1; i <= taskNum; i++) {
            taskList.add("Task " + i);
        }

        Consumer<String> consumer =
                item -> {
                    System.out.printf("Starting Task %s%n", item);
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException ignore) {
                    }
                    System.out.printf("Finished Task %s%n", item);
                };

        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            if (i == 2) {
                executeWithForecast(taskList, expireCostTime, forecastTimePreTask, consumer, null);
            }
        }
    }
}

计算说明

先看图:
线程数计算
好了,给大家介绍一下小学算术题:

我期望完成一个任务需要10min,根据经验或实际测验每个任务大概耗时1.5min左右。设当前总任务量为100,求合适的线程数量。

其实图中已经画得比较明白了,把总任务量看作大矩形,只要在10min内填满这个大矩形就ok了。
理想情况下,每个线程需要执行6~7轮,四舍五入,算作7轮。要在7轮内消耗完100个任务,那每轮就需要消耗14个任务,也就是线程数了。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值