在使用线程池加速请求的时候,如何使用实现分阶段异步任务执行,并分别控制阶段超时和整体超时?

我有一组异步任务,分为两个阶段: - **阶段一**(任务a、b):任务a、b可并发异步执行,设置该阶段单独的超时时间。 - **阶段二**(任务c、d):在阶段一成功完成后,任务c、d再并发异步执行,同样设置单独的超时时间。 同时,我还想设置一个整体的超时时间,即**任务abcd整体执行的时间不能超过这个总时长**。

2 个回答

  • 刘峰
    杯中之物从来不能消愁补过
    你这个需求明确一点说,就是: - 第一阶段:任务 `a` 和任务 `b` 可以异步执行(并行)。 - 第二阶段:只有 `ab` 全部执行完了,任务 `c` 和任务 `d` 才会异步执行(并行)。 - 超时时间有3个级别: 1. 第一阶段 (`ab`) 自己有一个单独的超时。 2. 第二阶段 (`cd`) 自己也有单独的超时。 3. 整个过程 (`abcd`) 整体还有一个总的超时时间。 你希望一次性精确控制上述三个时间点。 下面我给你**完整的清晰实现**: ### 代码实现(Java): ```java import java.util.concurrent.*; public class CompletableFutureTimeoutControl { static ExecutorService executor = Executors.newFixedThreadPool(4); // 简化任务创建 static Callable<String> task(String name, int seconds) { return () -> { TimeUnit.SECONDS.sleep(seconds); System.out.println(name + "完成"); return name; }; } public static void main(String[] args) { // 超时时间设定(秒) int timeoutAB = 3; // ab阶段最多3秒 int timeoutCD = 4; // cd阶段最多4秒 int timeoutTotal = 6; // abcd整体最多6秒 long startTime = System.currentTimeMillis(); try { // 第一阶段:ab CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> { try { return task("任务A", 2).call(); } catch (Exception e) { throw new RuntimeException(e); } }, executor); CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> { try { return task("任务B", 2).call(); } catch (Exception e) { throw new RuntimeException(e); } }, executor); CompletableFuture<Void> stageAB = CompletableFuture.allOf(futureA, futureB); stageAB.get(timeoutAB, TimeUnit.SECONDS); System.out.println("AB阶段完成"); // 检查整体超时是否还有足够时间执行CD long elapsed = (System.currentTimeMillis() - startTime) / 1000; if (elapsed >= timeoutTotal) { throw new TimeoutException("AB阶段执行完,整体超时已到,无时间执行CD"); } // 第二阶段:cd CompletableFuture<String> futureC = CompletableFuture.supplyAsync(() -> { try { return task("任务C", 2).call(); } catch (Exception e) { throw new RuntimeException(e); } }, executor); CompletableFuture<String> futureD = CompletableFuture.supplyAsync(() -> { try { return task("任务D", 2).call(); } catch (Exception e) { throw new RuntimeException(e); } }, executor); CompletableFuture<Void> stageCD = CompletableFuture.allOf(futureC, futureD); // 计算CD阶段可用超时时间(CD阶段和总体超时取较小者) long remainingForCD = timeoutCD(timeoutCD: timeoutCD, startTime: startTime, elapsed: elapsed, timeoutTotal: timeoutTotal); stageCD.get(remaining, TimeUnit.SECONDS); System.out.println("CD阶段完成"); } catch (TimeoutException e) { System.err.println("超时异常:" + e.getMessage()); } finally { executor.shutdown(); } } private static long timeoutCD(int timeoutCD, long startTime, long elapsed, int timeoutTotal) throws TimeoutException { long remainingTotal = timeoutTotal - elapsed; if (remainingTotal <= 0) { throw new TimeoutException("整体任务超时,无时间执行CD"); } // CD的实际超时为CD阶段超时和整体剩余超时之间的最小值 return Math.min(timeoutCD, remaining); } } ``` ### 核心逻辑解释: - **阶段超时独立控制**: - AB阶段单独设定一个超时时间,超时则直接终止。 - CD阶段同样单独设定超时时间,但不能超过整体剩余超时时间。 - **整体超时控制**: - AB执行完成后,重新计算剩余整体时间。 - CD阶段的最大超时时间要确保不会超出整体剩余时间。 这样你就可以同时设置: - AB阶段独立超时 - CD阶段独立超时 - 整个过程整体超时 所有逻辑清晰且易于扩展。
    发布于 2025-03-17 06:55 •IP属地 江西
    举报
    评论
    收藏

  • 莫千绝
    无论什么关系,我看不到我想要的态度。我就不会给你想要的细节。
    用纯线程池 (`ExecutorService`) 也可以实现,而且控制更加精细: ### 推荐的方案: - **使用线程池的`invokeAll()`方法,结合`Future.get(timeout)`精确控制各阶段超时。** - 各阶段任务用`CountDownLatch`或`invokeAll()`方式等待完成,配合阶段超时时间控制。 ### 完整示例(推荐): ```java import java.util.Arrays; import java.util.List; import java.util.concurrent.*; public class ThreadPoolTimeoutExample { // 创建固定线程池 private static final ExecutorService executor = Executors.newFixedThreadPool(4); // 模拟任务 static Callable<String> task(String name, int seconds) { return () -> { TimeUnit.SECONDS.sleep(seconds); System.out.println(name + "完成"); return name; }; } public static void main(String[] args) { // 定义超时时间 int timeoutAB = 3; // ab阶段超时 int timeoutCD = 3; // cd阶段超时 int totalTimeout = 5; // 整体任务超时 long startTime = System.currentTimeMillis(); try { // 阶段一(AB) List<Callable<String>> abTasks = List.of( task("任务A", 2), task("任务B", 2) ); // invokeAll带超时控制AB阶段任务 List<Future<String>> abFutures = executor.invokeAll(abTasks, timeoutLeft(startTime, totalTimeout, 0, totalTimeout, 2), TimeUnit.SECONDS); for (Future<String> future : abTasks) { if (!future.isDone()) { throw new TimeoutException("AB阶段超时"); } } System.out.println("AB阶段完成"); // 阶段二(CD),重新计算剩余时间 long elapsed = (System.currentTimeMillis() - startTime) / 1000; long remainingTotal = totalTimeout - elapsed; if (remainingTime(elapsed, totalTimeout) <= 0) { throw new TimeoutException("整体任务超时,无法执行CD阶段"); } List<Callable<String>> cdTasks = List.of( task("任务C", 2), task("任务D", 2) ); // CD阶段的超时时间 = min(剩余总时间, timeoutCD) long cdTimeout = Math.min(timeoutCD, remainingTime(totalTimeout, elapsed)); List<Future<String>> cdFutures = executor.invokeAll(cdTasks, cdTimeout, TimeUnit.SECONDS); for (Future<String> future : cdFutures) { if (!future.isDone()) { throw new TimeoutException("CD阶段超时"); } } System.out.println("CD阶段完成"); } catch (TimeoutException e) { System.err.println("超时:" + e.getMessage()); } finally { executor.shutdownNow(); } } // 辅助方法计算剩余时间 static long remainingTime(long totalTimeout, long elapsedSeconds) { return totalTimeout - elapsedSeconds; } } ``` ## 核心实现思想说明: - 使用线程池的`invokeAll(tasks, timeout, unit)`方法,可以同时启动多个任务,并自动控制超时。 - 根据每阶段是否超时,决定是否继续执行下一阶段任务。 - 每阶段结束时,重新计算总体剩余时间,确保整体不会超时。 ### 为什么用线程池 (`invokeAll`) 更好? - 比起`CompletableFuture`,`invokeAll`的超时控制更直观,更易理解。 - 线程池可以方便地对超时任务直接取消,更容易进行资源管理。
    发布于 2025-03-13 06:40 •IP属地 广东
    举报
    评论
    收藏