【问题标题】:shutting down ExecutorService in a spring boot Rest API在 Spring Boot Rest API 中关闭 ExecutorService
【发布时间】:2019-06-21 02:19:44
【问题描述】:

正在构建一个部署在 weblogic 12c 上的 spring boot rest api 应用程序。 我的要求之一是在每个传入请求上运行一些长时间运行的任务。 传入的休息请求可能会导致多个异步任务执行。

由于我不关心响应,也不关心这些任务会导致的任何异常,我选择使用 ExecutorService 而不是 Callable 或 CompletableFuture。

ExecutorService executorService =
  Executors.newFixedThreadPool(2, new CustomizableThreadFactory("-abc-"));

然后对于我在控制器中收到的传入请求,运行两个 for 循环并将这些任务分配给 ExecutorService:

for (final String orderId : orderIds) {
        for (final String itemId : itemIds) {               
            exec.execute(new Runnable() {
                public void run() { 
                    try {           
                         //call database operation
                    }catch(Throwable t) {
                       logger.error("EXCEPTION with {} , {}" ,orderId,itemId 
                    )
                }   
            });
        }//for          
    }//for

我的问题是关于关闭 ExecutorService。 我知道正常关机(shutdown)、混合关机(awaitTermination)或突然关机(shutdownNow)

对于 rest api 应用程序,这三者之间的首选方法是什么?

对于可以创建的线程池的数量是否有任何限制,即创建的 ExecutorService 线程池的数量将由传入请求的数量驱动

【问题讨论】:

    标签: java spring multithreading weblogic executorservice


    【解决方案1】:

    我们目前有类似的要求,这是一个很难解决的问题,因为您想使用正确的锤子。有很多重量级的解决方案来编排长时间运行的进程,例如 SpringBatch。

    首先,尽管不要打扰停止和启动 ExecutorService。该类的全部意义在于将线程管理的负担从您手中卸下,因此您无需自己创建和停止线程。所以你不需要管理经理。

    但要小心你的方法。无需使用队列或其他负载平衡技术来智能地平衡应用程序中跨实例的长时间运行的进程。或者管理线程死亡时发生的事情,您可能会陷入困境。总的来说,我会说现在直接与线程或线程池交互并使用更高级别的解决方案来解决这类问题没有多大意义。

    【讨论】:

    • 当你说不要打扰 ExecutorService 的启动/停止 - 因为我将 ExecutorService 创建为局部变量 - 你是否暗示在方法(和所有线程)完成后 ExecutorService 将获得 gced所以我不应该打扰? (我不知何故觉得我没有通过将 ExecutorService 实例化为局部变量来正确编码)。关于线程何时死亡 - 根据我对这里 #6 nurkiewicz.com/2014/11/executorservice-10-tips-and-tricks.html 的理解,我将它放在 try catch 中。也许我错过了你的观点
    • 也只是想知道控制器是否只是使用@Aync 注释调用我的服务 - 这是一个选项......
    • 您可以在单例 bean 中拥有执行服务,这应该将您的实际流程与您的 ExecutionService 分离。 @Async 当您想立即向客户端返回响应时需要,如果您在进程完成之前一直阻塞,则仅在您的情况下才需要。除非您想完全取消您的 ExecutionService,而只需使用 @Async
    • 谢谢,是的,我的这个端点的唯一要求是一个接一个地触发并忘记多个任务(数据库操作),而不是等待所有任务的结果。根据您上面的 cmets,我完全不愿意尝试拼凑我自己的 ExecutionService。所以看起来@Async 将完成我的工作,对于有效负载中的每个 orderId,我将生成对我的服务的异步调用并返回 202 的响应(正在进行中)。消费者将调用一个单独的 API 端点来检查这些任务的状态。
    【解决方案2】:

    awaitTermination 通常更安全一些,而shutdownNow 更有力。如果您希望执行程序尽快关闭,但仅在它完成创建它要执行的所有操作之后,在函数方法甚至可运行方法中使用 awaitTermination 通常是一个好主意。换句话说,当执行器没有正在执行的活动任务时。 例如)

    ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors);
    Observable.of(items).schedule(Schedulers.from(executor)).flatMap(item -> {
       ... // this block represents a task that the executor will execute in a worker thread
    }).onSubscribe(onNext -> 
       logItem(onNext), throwable -> 
       throwable.printStackTrace(), /* onComplete */ () -> 
       executor.awaitTermination(60, TimeUnit.Seconds)
    );
    ... // you need to shutdown asap because these other methods below are also doing some computation/io-intensive stuff
    

    现在,当这个方法完成时,它会调用awaitTermination,如果它没有执行任何任务,它将立即关闭池,或者如果任务仍在执行,则等待长达 60 秒。

    在大多数情况下,如果 60 秒不活动,线程或工作人员将停止活动,因为这通常是默认设置。

    另一方面,如果您希望任务在抛出异常、安全漏洞或另一个模块/服务失败时立即停止执行(举一些例子),您可能需要使用 shutdownNow () 立即停止所有任务,无需等待。

    如果您不希望在出现异常时继续执行任务 - 即不再有理由返回列表,我在两者之间进行选择的建议是在您的 catch 块中使用 shutdownNow鉴于其中一项未添加到列表中,向客户提供的项目。 否则,我建议在您的 try-catch 设置为一分钟后使用awaitTermination,以便在线程池执行完您给它的所有任务后立即安全地关闭线程池。但只有在您知道执行者将不负责执行任何更多任务的情况下才这样做。

    简单的shutdown,如果你可以选择的话,也是一个好方法。根据 Oracle 文档,shutdown 将拒绝所有传入的任务,但等待当前任务完成执行。

    如果您不确定何时需要关闭执行程序,最好使用@PreDestroy 方法,以便执行程序在您的 bean 上调用 destroy 方法之前执行:

    @PreDestroy
    private void cleanup(){
       executor.shutdown();
    }
    

    【讨论】:

    • 我在很大程度上同意@Snickers3192。管理你自己的线程池肯定会给你带来很多麻烦。出于这个原因,通常不建议这样做,特别是如果您计划在整个应用程序的多个位置创建其中的几个。不过,在某些情况下,它们要么是必要的,要么是显着的性能提升器。
    • 谢谢,我会回复 Snickers3192 的更多疑问/疑问 - 但感谢您的回复 - 让我取得了进展
    猜你喜欢
    • 1970-01-01
    • 2021-08-28
    • 2019-03-11
    • 2021-04-13
    • 2020-11-13
    • 1970-01-01
    • 2019-04-19
    • 2020-03-15
    • 2023-03-25
    相关资源
    最近更新 更多