多个 Future 对象代表的异步任务的3种方式及优缺点

在 Java 中,当有多个 Future 对象代表的异步任务都完成后再进行下一步处理,有多种实现方式

1. 使用 CountDownLatch


CountDownLatch 是 Java 并发包中的一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。可以为每个 Future 任务关联一个 CountDownLatch,当每个任务完成时减少计数器,当计数器减为 0 时,表示所有任务都已完成。
 
 
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class FutureWithCountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        int taskCount = 3;
        CountDownLatch latch = new CountDownLatch(taskCount);
        List<Future<Integer>> futures = new ArrayList<>();

        // 提交多个异步任务
        for (int i = 0; i < taskCount; i++) {
            final int index = i;
            Future<Integer> future = executor.submit(() -> {
                try {
                    // 模拟任务执行
                    Thread.sleep((index + 1) * 1000);
                    System.out.println("Task " + index + " completed.");
                    return index * 10;
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return -1;
                } finally {
                    // 任务完成,减少计数器
                    latch.countDown();
                }
            });
            futures.add(future);
        }

        // 等待所有任务完成
        latch.await();

        // 处理所有任务的结果
        for (Future<Integer> future : futures) {
            try {
                System.out.println("Result: " + future.get());
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        executor.shutdown();
    }
}

 

2. 使用 Java 8 的 CompletableFuture


CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,它可以方便地处理多个异步任务的组合和完成情况。可以使用 CompletableFuture.allOf 方法等待所有 CompletableFuture 完成。
 
 
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CompletableFutureExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 创建多个 CompletableFuture 任务
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Task 1 completed.");
            return 10;
        }, executor);

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Task 2 completed.");
            return 20;
        }, executor);

        CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Task 3 completed.");
            return 30;
        }, executor);

        // 创建一个新的 CompletableFuture,当所有任务都完成时触发
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);

        // 当所有任务完成后,处理结果
        CompletableFuture<List<Integer>> allResults = allFutures.thenApply(v -> {
            return List.of(future1.join(), future2.join(), future3.join());
        });

        // 等待结果并处理
        allResults.thenAccept(results -> {
            for (Integer result : results) {
                System.out.println("Result: " + result);
            }
        }).join();

        executor.shutdown();
    }
}

 

3. 手动轮询检查


通过循环不断检查每个 Future 的 isDone() 方法,直到所有 Future 都完成。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class ManualPollingExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        List<Future<Integer>> futures = new ArrayList<>();

        // 提交多个异步任务
        for (int i = 0; i < 3; i++) {
            final int index = i;
            Future<Integer> future = executor.submit(() -> {
                try {
                    Thread.sleep((index + 1) * 1000);
                    System.out.println("Task " + index + " completed.");
                    return index * 10;
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return -1;
                }
            });
            futures.add(future);
        }

        // 手动轮询检查所有任务是否完成
        boolean allDone = false;
        while (!allDone) {
            allDone = true;
            for (Future<Integer> future : futures) {
                if (!future.isDone()) {
                    allDone = false;
                    break;
                }
            }
        }

        // 处理所有任务的结果
        for (Future<Integer> future : futures) {
            try {
                System.out.println("Result: " + future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        executor.shutdown();
    }
}

 

以上三种方法各有优缺点,CountDownLatch 适用于传统的并发编程场景;CompletableFuture 是 Java 8 及以后版本推荐的方式,提供了更丰富的异步编程功能;手动轮询检查简单直接,但效率较低,不适合大规模任务的场景。
 

在生产代码中,使用 Java 8 引入的 CompletableFuture 通常是最合适的选择,以下从几个方面详细分析它相较于 CountDownLatch 和手动轮询的优势:

代码简洁性与可读性

  • CompletableFutureCompletableFuture 提供了一系列丰富的方法,如 thenApplythenAcceptthenComposeallOf 等,这些方法可以让你以链式调用的方式来组合和处理异步任务,代码结构清晰,易于理解和维护。例如,使用 CompletableFuture.allOf 可以轻松地等待多个异步任务完成,再通过 thenApply 或 thenAccept 处理结果。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
CompletableFuture<List<Integer>> allResults = allFutures.thenApply(v -> 
    List.of(future1.join(), future2.join())
);
allResults.thenAccept(results -> {
    for (Integer result : results) {
        System.out.println("Result: " + result);
    }
});
  • CountDownLatch:使用 CountDownLatch 需要手动管理计数器的增减,并且在任务完成时需要在 finally 块中确保计数器被正确减少,代码相对繁琐。
CountDownLatch latch = new CountDownLatch(2);
Future<Integer> future1 = executor.submit(() -> {
    try {
        return 10;
    } finally {
        latch.countDown();
    }
});
Future<Integer> future2 = executor.submit(() -> {
    try {
        return 20;
    } finally {
        latch.countDown();
    }
});
try {
    latch.await();
    System.out.println("Result 1: " + future1.get());
    System.out.println("Result 2: " + future2.get());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

 

  • 手动轮询:手动轮询的代码需要不断地检查每个 Future 的状态,逻辑较为复杂,并且会占用额外的 CPU 资源,代码可读性较差。
List<Future<Integer>> futures = new ArrayList<>();
// 提交任务并添加到 futures 列表
boolean allDone = false;
while (!allDone) {
    allDone = true;
    for (Future<Integer> future : futures) {
        if (!future.isDone()) {
            allDone = false;
            break;
        }
    }
}
// 处理结果
 

错误处理

  • CompletableFutureCompletableFuture 提供了专门的异常处理方法,如 exceptionallyhandle 等,可以方便地处理异步任务中抛出的异常,并且可以在链式调用中灵活地处理不同阶段的异常。
 
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() < 0.5) {
        throw new RuntimeException("Something went wrong");
    }
    return 10;
}).exceptionally(ex -> {
    System.out.println("Exception caught: " + ex.getMessage());
    return -1;
});

  • CountDownLatch:在使用 CountDownLatch 时,异常处理需要在每个 Future 的任务中手动捕获和处理,并且在等待 CountDownLatch 时还需要处理 InterruptedException,代码较为复杂。
 
Future<Integer> future = executor.submit(() -> {
    try {
        if (Math.random() < 0.5) {
            throw new RuntimeException("Something went wrong");
        }
        return 10;
    } catch (RuntimeException ex) {
        // 处理异常
        return -1;
    }
});
try {
    latch.await();
    Integer result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

  • 手动轮询:手动轮询的异常处理同样需要在每个 Future 的任务中手动处理,并且在获取结果时还需要处理 ExecutionException 和 InterruptedException,容易出现遗漏。

异步组合与流式处理

  • CompletableFutureCompletableFuture 支持将多个异步任务进行组合,如串行执行、并行执行、分支合并等,还可以进行流式处理,非常适合复杂的异步场景。例如,可以使用 thenCompose 实现异步任务的串行执行,使用 thenCombine 实现两个异步任务的结果合并。
 
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = future1.thenCompose(result -> 
    CompletableFuture.supplyAsync(() -> result * 2)
);
CompletableFuture<Integer> future3 = future1.thenCombine(future2, (r1, r2) -> r1 + r2);

  • CountDownLatchCountDownLatch 主要用于简单的等待多个任务完成的场景,对于复杂的异步任务组合和流式处理支持不足。
  • 手动轮询:手动轮询更侧重于检查任务的完成状态,无法方便地进行异步任务的组合和流式处理。

性能与资源管理

  • CompletableFutureCompletableFuture 内部使用了 ForkJoinPool 等线程池机制,能够更好地管理线程资源,避免线程创建和销毁带来的开销,提高性能。同时,它还支持自定义线程池,以满足不同的性能需求。
  • CountDownLatch:使用 CountDownLatch 时需要手动创建和管理线程池,如果线程池配置不合理,可能会导致资源浪费或性能问题。
  • 手动轮询:手动轮询会不断地检查 Future 的状态,占用额外的 CPU 资源,尤其是在任务数量较多或检查频率较高时,会对性能产生较大影响。

综上所述,CompletableFuture 在代码简洁性、错误处理、异步组合、性能和资源管理等方面都具有明显的优势,因此在生产代码中是处理多个 Future 完成后进行下一步操作的首选方案。
posted @ 2025-02-24 15:09  蜡笔小新DD  阅读(37)  评论(0)    收藏  举报