引例
最近帮以前的老东家,现在的兼职雇主做一些优化,比较多的是性能这一块。今天心血来潮想着每次自己计算线程池的线程数量太麻烦了,要是能有个办法能自动计算线程数量,创建线程池,并执行任务那就方便了。
代码
先上代码,附上贴心测试哟~
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个任务,也就是线程数了。