在 ExecutorService 上调用 shutdown() 的原因

Posted

技术标签:

【中文标题】在 ExecutorService 上调用 shutdown() 的原因【英文标题】:Reason for calling shutdown() on ExecutorService 【发布时间】:2013-04-13 22:20:42 【问题描述】:

在过去的几个小时里,我读了很多关于它的文章,但我根本看不出有任何理由(有效理由)在 ExecutorService 上致电 shutdown(),除非我们有一个巨大的应用程序,存储了数十种很长时间未使用的不同执行器服务。

唯一的事情(据我所知)关闭所做的就是做一个普通线程在完成后所做的事情。当普通的 Thread 完成 Runnable(或 Callable)的 run 方法后,会被传递给 Garbage Collection 进行收集。使用 Executor Service,线程将被搁置,不会为垃圾收集打勾。为此,需要关机。

好的,回到我的问题。是否有任何理由经常在ExecutorService 上调用关机,甚至在提交一些任务后立即调用它?我想留下有人正在这样做的情况,然后立即致电awaitTermination(),因为这是经过验证的。一旦我们这样做了,我们必须重新创建一个新的ExecutorService,来做同样的事情。 ExecutorService 的全部想法不是重用线程吗?那么为什么这么快就销毁ExecutorService呢?

简单地创建ExecutorService(或根据您需要多少个),然后在应用程序运行期间将任务传递给他们,然后在应用程序退出或一些其他重要阶段关闭这些执行器?

我想从一些使用 ExecutorServices 编写大量异步代码的经验丰富的程序员那里得到答案。

第二个问题,与 android 平台有关的小一点的交易。如果你们中的一些人会说每次都关闭执行程序并不是最好的主意,并且你的程序在 android 上,你能告诉我当我们处理不同的事件时你如何处理这些关闭(具体来说 - 当你执行它们时)应用程序生命周期。

由于 CommonsWare 评论,我将帖子设为中立。我真的没有兴趣争论到死,而且似乎它正在导致那里。我只对了解我在这里向经验丰富的开发人员提出的问题感兴趣,他们是否愿意分享他们的经验。谢谢。

【问题讨论】:

“我多次看到示例代码,其中一直存在在提交或执行任务之后的 shutdown() 调用”——随意使用超链接来提供您的声明的证据。就个人而言,我从未见过任何按照您所说的“示例代码”。您可能会误解某些内容,如果我们知道您正在检查哪些“示例代码”,我们只能向您指出这一点。 嗨 CommonsWare。首先,我看到你(或看起来)对我的攻击性语气,我认为这里没有得到证实。我并没有试图以消极的方式描绘人们。至于你的引述,我主要是在谈论 Thinking In Java IV 版本,多任务部分。您可以在 Bruce Eckel 的示例中找到很多这样的例子。它们大多很简单,但布鲁斯给我的印象是经常使用关机。无论如何,您关注的不是我帖子的主要部分。我删除了这些部分,因为我真的不想为此争论。 hay @CommonsWare in Thinking in java book by Bruce Eckel..in concurrency/Executor page 804 Fourth Edition ,他总是在简单应用程序中提交或执行任务后立即使用shutdown()方法来说明Executor如何正如卢卡斯所说的那样工作 我知道这是一篇旧帖子,但我认为 OP 的问题仍然存在并且有效。我还遇到了许多示例代码,其中“在 execute() 之后有一个 shutdown() 调用”。 tutorials.jenkov.com/java-util-concurrent/executorservice.html(谷歌“java executorservice example”时出现的第一个教程) 谢谢,我遇到了与这些“示例代码”相同的问题。 journaldev.com/2340/… 【参考方案1】:

shutdown() 方法做了一件事:阻止客户端向执行器服务发送更多工作。这意味着除非采取其他行动,否则所有现有任务仍将运行完成。即使对于计划任务也是如此,例如,对于 ScheduledExecutorService:计划任务的新实例不会运行。它还释放任何后台线程资源。这在各种情况下都很有用。

假设您有一个控制台应用程序,它有一个执行器服务运行 N 个任务。如果用户按下 CTRL-C,您希望应用程序终止,可能是正常的。优雅是什么意思?也许您希望您的应用程序无法向执行器服务提交更多任务,同时您希望等待现有的 N 个任务完成。作为最后的手段,您可以使用关闭挂钩来实现此目的:

final ExecutorService service = ... // get it somewhere

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() 
    @Override
    public void run() 
        System.out.println("Performing some shutdown cleanup...");
        service.shutdown();
        while (true) 
            try 
                System.out.println("Waiting for the service to terminate...");
                if (service.awaitTermination(5, TimeUnit.SECONDS)) 
                    break;
                
             catch (InterruptedException e) 
            
        
        System.out.println("Done cleaning");
    
));

此钩子将关闭服务,这将阻止您的应用程序提交新任务,并在关闭 JVM 之前等待所有现有任务完成。 await 终止将阻塞 5 秒,如果服务关闭则返回 true。这是在循环中完成的,因此您确定服务最终会关闭。 InterruptedException 每次都被吞没。这是关闭在整个应用程序中重用的执行程序服务的最佳方法。

这段代码并不完美。除非您绝对肯定您的任务最终会终止,否则您可能希望等待给定的超时时间然后退出,放弃正在运行的线程。在这种情况下,在超时后也调用shutdownNow() 以最终尝试中断正在运行的线程是有意义的(shutdownNow() 还会为您提供等待运行的任务列表)。如果您的任务旨在响应中断,这将正常工作。

另一个有趣的场景是当您有一个 ScheduledExecutorService 执行周期性任务时。停止周期性任务链的唯一方法是调用shutdown()

编辑:我想补充一点,我不建议在一般情况下使用如上所示的关闭挂钩:它可能容易出错,并且只能作为最后的手段。此外,如果您注册了许多关闭挂钩,则它们的运行顺序是不确定的,这可能是不可取的。我宁愿让应用程序在InterruptedException 上显式调用shutdown()

【讨论】:

抱歉 Giovanni 回复晚了,顺便说一句谢谢。是的,我知道 Executor 的工作原理,我试图在我的问题中解释这一点。关闭按照您所说的进行,它还允许垃圾收集器收集那些死线程,并实际上收集 ExecutorService 实例。我的问题很具体。在您在 ExecutorService 上提交/执行任何内容之后,是否有任何理由一直调用“shutdown()”。问题的第二部分严格与 Android 架构有关。如果前面的答案是否定的,那么在and期间何时调用shutdown。生命周期。 没有理由一直调用shutdown()。事实上,这可能是绝对错误的做法,因为它会阻止您再次重用执行器服务。在服务生命周期结束时调用它的原因是,线程最终可以像您注意到的那样被垃圾收集。如果你不这样做,即使它们处于空闲状态,这些线程也会让 JVM 保持活动状态。 “没有理由一直调用shutdown()。事实上,这可能是绝对错误的做法,因为它会阻止你再次重用执行器服务”。这正是我的推理,也是我对原始问题的困境。所以重申一下,问题是:我应该在 Android 生命周期中什么时候关闭我的 ExecutiveService? 我没有Android经验,但我猜你应该在你的应用程序关闭时关闭它,以便让JVM最终退出。 我明白了。我建议您使用cached thread pool 并且永远不要在其上调用shutdown(),这样您就不会在不必要的时候浪费资源。如果应用程序确实关闭了,池中的线程最终将被垃圾回收(默认情况下它们空闲 60 秒后)。请注意,如果您希望池有界或想要不同的线程生命周期,您可以直接创建 ThreadPoolExecutor【参考方案2】:

ExecutorService 重用线程的整个想法不就是这样吗?那为什么这么快就销毁 ExecutorService 呢?

是的。你不应该经常销毁和重新创建ExecutorService。当您需要时(主要是在启动时)初始化ExecutorService 并保持它处于活动状态,直到您完成它。

简单地创建 ExecutorService (或根据您需要多少),然后在应用程序运行期间将任务传递给它们,然后在应用程序退出或其他一些重要的阶段关闭那些执行者?

是的。在应用程序退出等重要阶段关闭ExecutorService 是合理的。

第二个问题,与 android 平台有关的小一点的交易。如果你们中的一些人会说每次都关闭执行程序不是最好的主意,并且您在 android 上编程,您能否告诉我当我们处理不同的应用程序事件时您如何处理这些关闭(具体来说,当您执行它们时)生命周期。

假设ExecutorService 在您的应用程序中的不同活动之间共享。每个活动将在不同的时间间隔暂停/恢复,但您仍然需要为每个应用程序提供一个 ExecutorService

不要在 Activity 生命周期方法中管理 ExecutorService 的状态,而是将 ExecutorService 管理(创建/关闭)移动到您的自定义 Service。

在服务中创建ExecutorService => onCreate() 并在onDestroy() 中正确关闭它

推荐的关闭方式ExecutorService

How to properly shutdown java ExecutorService

【讨论】:

我认为这个 lifeCycle 依赖问题是相对的,因为如果某些东西仍在向您发送可运行的命令,并且如果您的执行程序是 .shutdown() 通过 lifeCycle 终止,则整个类仍然需要从发送命令的生产者那里取消引用(如果不这样做,它会泄漏)。这意味着:如果整个 LifeCycle 系统正确完成,并且每个 Consumer 都从其 Producer 中正确取消引用,则每个 .shutdown() 都将是多余的,因为 executor 的整个外壳仍然有资格进行垃圾收集。 现在,如果选择的阶段是 onStart() 用于创建 + onStop() 用于 shutdown();【参考方案3】:

一旦不再需要 ExecutorService 就应该关闭它 释放系统资源并允许正常关闭应用程序。 因为 ExecutorService 中的线程可能是非守护线程, 它们可能会阻止正常的应用程序终止。换句话说,你的 应用程序在完成其 main 方法后保持运行。

Reference Book

第 14 章 第814页

【讨论】:

【参考方案4】:

在 ExecutorService 上调用 shutdown() 的原因

今天我遇到了一种情况,我必须等到机器准备好,然后才能在该机器上开始一系列任务。

我对这台机器进行了 REST 调用,如果我没有收到 503(服务器不可用),那么这台机器已准备好处理我的请求。所以,我等到第一次 REST 调用获得 200(成功)。

有多种实现方式,我使用 ExecutorService 创建一个线程并安排它在每 X 秒后运行。所以,我需要在一个条件下停止这个线程,检查一下......

final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    Runnable task = () -> 
        try 
            int statusCode = restHelper.firstRESTCall();

            if (statusCode == 200) 
                executor.shutdown();
            
         catch (Exception e) 
            e.printStackTrace();
        
    ;

    int retryAfter = 60;
    executor.scheduleAtFixedRate(task, 0, retryAfter, TimeUnit.SECONDS);

第二个问题,与android平台的交易有点小。

如果您能提供更多背景信息,也许我可以回答! 同样根据我在 Android 开发方面的经验,很少需要线程。您正在开发需要线程来提高性能的游戏或应用程序吗?如果没有,在 Android 中,您还有其他方法可以解决我上面解释的场景等问题。您可以根据上下文使用 TimerTask、AsyncTask 或 Handlers 或 Loaders。这是因为如果 UIThread 等待很长时间,您就会知道会发生什么:/

【讨论】:

【参考方案5】:

这对于计划中的任务来说是真实的,例如,对于 ScheduledExecutorService:预定任务的新案例将不会运行。

我们应该期望您有一个舒适的应用程序,它有一个运行 N 差事的代理管理。

我没有毫不费力地理解它的意思?也许您需要您的应用程序不能选择向代理管理提交更多任务,同时您需要坐好等待您当前的 N 项任务完成。

除非您完全确定您的差事最终会完成,否则您应该在给定的休息时间坐好,然后简单地退出,抛弃正在运行的字符串。

如果您的活动旨在对干扰做出反应,这将正常工作。

另一个有趣的情况是你有一个 ScheduledExecutorService 来播放一个活动。

停止活动链的最好方法是调用shutdown()

【讨论】:

以上是关于在 ExecutorService 上调用 shutdown() 的原因的主要内容,如果未能解决你的问题,请参考以下文章

ExecutorService 导致 JDBC 连接问题

使用CompletionService结合ExecutorService批处理调用存储过程任务实例

ExecutorService vs CompletableFuture

ExecutorService和CompletionService区别

如何关闭 ExecutorService?

什么是 FutureTask?使用 ExecutorService 启动任务?