首页 > Java > java教程 > 掌握 ExecutorService 关闭:跟踪线程池终止

掌握 ExecutorService 关闭:跟踪线程池终止

Patricia Arquette
发布: 2025-01-05 01:50:38
原创
929 人浏览过

Mastering ExecutorService Shutdown: Tracking ThreadPool Termination

假设您想要执行一些任务。由于通过单个线程执行它可能需要相当长的时间才能获得结果,因此您决定使用可靠的 ExecutorService 通过多个线程处理它。

这是一个示例:

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 5; i++) {
        int temp = i;
        executorService.submit(() -> {
            task(temp);
        });
    }
    executorService.shutdown();
    System.out.println("ExecutorService is shutdown");
}

private static void task(int temp) {
    try {
        TimeUnit.SECONDS.sleep(1L);
        System.out.println("Task " + temp + " completed");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
登录后复制
登录后复制

当然,像往常一样,如果不使用“睡眠”作为任务执行的原型,任何线程示例都是不完整的。

它输出,

ExecutorService is shutdown
Task 1 completed
Task 2 completed
Task 0 completed
Task 4 completed
Task 3 completed
登录后复制
登录后复制

现在想象一下有一个无穷无尽的任务队列,而你不知道其中的数量。也许它们是由数据库中动态添加的条目数量决定的。

例如,一家银行,它必须全天处理大量交易。交易结束时间为下午5点,超过该时间将不再接受任何额外任务。

但是,您确实知道任务的数量是有限的,并且在某个时间点结束。

如何知道所有任务完成的时间点?

如果您注意到上面的代码片段,ExecutorService.shutdown() 会使主线程立即退出,但后台线程仍然处理已接受的任务直至完成。有什么方法可以让您收到完成通知吗?

我想到了几个解决方案:

  1. 使用 CountDownLatch 来计算任务 - 但由于您不知道任务的数量,所以使用它是不切实际的。
  2. 使用ExecutorService.awaitTermination()。然而,这里的时间仍然是不确定的。您可以使用非常自由的 ExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS) 或类似的东西。但这又是一个阻塞调用。

有更好的方法来解决这个问题吗?

Java 确实提供了一种更好且相对未知的方法来解决这个问题。这里的“技巧”是要知道 Executors.newFixedThreadPool 本质上是一个具有预定义值的 ThreadPoolExecutor。我们来看看 Executors.newFixedThreadPool 的实现。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
登录后复制
登录后复制

我真的建议阅读这里的 ThreadPoolExecutor 文档。 ExecutorService 是 ThreadPoolExecutor 的便捷包装。

...强烈建议程序员使用更方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,具有自动线程回收功能)、Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleThreadExecutor( )(单后台线程),为最常见的使用场景预配置设置。

将帮助我们解决问题的部分是:

钩子方法

此类提供了受保护的可重写 beforeExecute(Thread, Runnable) 和 afterExecute(Runnable, Throwable) 方法,这些方法在每个任务执行之前和之后调用。这些可用于操纵执行环境;例如,重新初始化 ThreadLocals、收集统计信息或添加日志条目。此外,可以重写方法终止()以执行执行器完全终止后需要完成的任何特殊处理。

我们可以使用终止方法来通知我们同样的事情!但我们该如何使用它呢?

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 5; i++) {
        int temp = i;
        executorService.submit(() -> {
            task(temp);
        });
    }
    executorService.shutdown();
    System.out.println("ExecutorService is shutdown");
}

private static void task(int temp) {
    try {
        TimeUnit.SECONDS.sleep(1L);
        System.out.println("Task " + temp + " completed");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
登录后复制
登录后复制

如果您不喜欢匿名类(像我一样),您始终可以自己扩展 ThreadPoolExecutor 来创建自定义类。

ExecutorService is shutdown
Task 1 completed
Task 2 completed
Task 0 completed
Task 4 completed
Task 3 completed
登录后复制
登录后复制

这是验证它是否按照我们的预期工作的输出。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
登录后复制
登录后复制

您还使用了哪些其他相对未知的片段?请在评论中告诉我!

以上是掌握 ExecutorService 关闭:跟踪线程池终止的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板