如何检查是否所有的Runnable都完成了

2023/06/07

1. 概述

在本文中,我们将获取一个Runnable对象列表并检查它们是否都已完成。正如我们所知,Runnable是一个接口,其实例可以作为Thread运行。我们将使用诸如CompletableFuture和ThreadPoolExecutor之类的包装对象来运行这些线程。

2. 示例设置

让我们创建一个基本的Runnable,它只会记录一条消息,然后暂停1秒:

static Runnable RUNNABLE = () -> {
    try {
        System.out.println("launching runnable");
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
};

现在,我们将创建一个Runnable列表。在此示例中,我们将重复添加相同的Runnable。实现此目的的一种方法是使用IntStream

List<Runnable> runnables = IntStream.range(0, 5)
    .mapToObj(x -> RUNNABLE)
    .collect(Collectors.toList());

现在让我们看看如何运行这些Runnable对象并了解它们是否全部完成。

3. 使用CompletableFuture

从Java 8开始,我们可以使用内置的CompletableFuture的isDone()方法来达到这个目的

CompletableFuture对象使Java中的异步编程更加容易。鉴于我们的Runnable列表,我们将使用CompletableFuture的runAsync()方法异步运行相关任务。请注意,默认情况下,所有这些任务都将在ForkJoinPool上运行。

为了进一步的目的,我们希望将所有结果的CompletableFuture包装在一个数组中:

CompletableFuture<?>[] completableFutures = runnables.stream()
    .map(CompletableFuture::runAsync)
    .toArray(CompletableFuture<?>[]::new);

现在,我们所有的Runnable任务都被包装到CompletableFuture执行中。这意味着这些任务将在我们的程序继续运行时在后台异步运行。

为了查明我们程序中的任何一点是否所有执行都已完成,我们将从我们的数组中创建一个新的包装CompletableFuture。allOf()方法将允许我们这样做。然后,我们将isDone()方法直接应用于包装的CompletableFuture:

boolean isEveryRunnableDone = CompletableFuture.allOf(completableFutures)
    .isDone();

如果CompletableFuture中的任何一个仍在运行,则isEveryRunnableDone将为false,否则将为true。

4. 使用ThreadPoolExecutor

从Java 5开始,线程池提供了额外的工具来帮助管理并发环境中的资源。特别是,他们维护一些统计数据,例如他们持有的已完成任务的数量。

4.1 统计剩余任务数

让我们创建一个具有5个线程的ThreadPoolExecutor。然后,我们将使用execute()方法提交每个Runnable以供执行:

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
runnables.forEach(executor::execute);

现在我们可以使用getActiveCount()方法计算ThreadPoolExecutor中正在运行的任务数

int numberOfActiveThreads = executor.getActiveCount();

这里的问题是,我们是否可以将这个数字与0进行比较,以检查是否有任何Runnable仍在运行?事情实际上比这要复杂一点。问题在于getActiveCount()方法返回的数字是一个近似值,如类的文档所述。因此,我们不能依赖它来做出任何决定

4.2 检查是否所有任务都已终止

getActiveCount()方法不会返回准确的值,因为这样做可能需要大量计算。因此,让我们立即放弃实现我们自己的计数器的选项。

另一方面,awaitTermination()方法可以让我们知道是否所有任务都已完成。但首先,我们需要调用执行器的shutdown()方法。调用此方法将确保所有提交的任务都将完成。但是,它会阻止将新任务添加到执行器:

executor.shutdown();

我们已确保我们的ThreadPoolExecutor将正确关闭。我们现在可以通过调用awaitTermination()随时检查池中是否有任何正在运行的任务。此方法将阻塞,直到给定超时或直到所有任务完成。例如,为了我们的示例,让我们使用1秒超时:

boolean isEveryRunnableDome = executor.awaitTermination(1000, TimeUnit.MILLISECONDS);

如果所有任务在1秒内完成,该方法立即返回true。否则,程序将被阻塞一秒钟,然后返回false。

最后但同样重要的是,我们应该注意,如果任何底层线程被中断,awaitTermination()将抛出InterruptedException

5. 总结

在本教程中,我们了解了如何检查所有Runnable是否已完成。对于高于8的Java版本,由于CompletableFuture类,这非常简单。对于旧版本,我们需要明智地选择超时,因为程序可能会在我们设置的持续时间内被阻塞。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章