关闭两个ExecutorService实例。

3

我需要在一个方法中正确关闭两个 Executor Service 实例。

这是我简化后的代码:

ExecutorService executor1 = Executors.newSingleThreadExecutor();
ScheduledExecutorService executor2 = Executors.newSingleThreadScheduledExecutor();
// logic here
executor1.shutdown();
executor2.shutdown();
try {
    if (!executor1.awaitTermination(1, TimeUnit.SECONDS)) {
        executor1.shutdownNow();
    }
} catch (InterruptedException ex) {
    throw new IllegalStateException(ex);
}
try {
    if (!executor2.awaitTermination(1, TimeUnit.SECONDS)) {
        executor2.shutdownNow();
    }
} catch (InterruptedException ex) {
    throw new IllegalStateException(ex);
}

InterruptedException被转换为IllegalStateException,因为我不希望在这里发生任何中断,如果发生中断会导致我的应用程序进入非法状态。

我认为这个解决方案存在一个缺陷 - 每当第一个执行器在关闭时抛出异常时,第二个执行器将无法正确关闭。在这里应该采取什么正确的方法?如何安全地关闭两个ExecutorService实例?

我宁愿避免嵌套的try-finally块,因为我可能需要添加第三个执行器服务,代码会变得难以管理。


这和这个网页是一样的吗:http://stackoverflow.com/questions/25330464/running-multiple-thread-pools-executorservice-together? - Yaco Zaragoza
@YacoZaragoza 不完全是这样 - 这里我的代码示例是简化的,但我需要两个单独的执行器服务,因为一个是普通的,但第二个是定时执行器服务。 - Michal Kordas
明白了,让我运行一些测试 :-) - Yaco Zaragoza
当两个关闭引发异常时,您想保留哪一个?如果您想保留两者,您需要一个包装异常来包含它们。 - dhke
如果第一个出错则保留信息,然后处理第二个,最后在处理完之前安排的所有程序后抛出异常。 - Fildor
@dhke 一个足以,第二个最好能够被抑制。我更喜欢使用一些实用工具来完成这个任务,因为我不想在我的应用程序中添加太多样板代码。 - Michal Kordas
1个回答

2

类似的情况下:

Apache Commons IO提供了一个 closeQuietly() 方法,可以关闭流(或者任何可关闭的对象Closeable),并忽略在关闭期间抛出的异常。

public void shutdownQuietly(ExecutorService executor)
{
    try {
        if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
            executor.shutdownNow();
        }
    } catch (InterruptedException ex) {
       /* IGNORE */
    }  
}

如果您需要这些异常,您可以尝试一些稍微更加邪恶的技巧:

class MultiExecutorShutdown
{
     private final List<InterrupedException> exceptions = new ArrayList<>();

     public void shutdown(ExecutorService service)
     {
         try {
             if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
                executor.shutdownNow();
             }
         } catch (InterruptedException ex) {
             exceptions.add(ex);
         }
     }

     public Optional<InterruptedException> getLastException()
     {
         if (exceptions.isEmpty()) {
            return Optional.empty();
         } else {
             return exceptions.get(exceptions.size() - 1);
         }
     }

     public Optional<InterruptedException> getFirstException()
     {
         if (exceptions.isEmpty()) {
            return Optional.empty();
         } else {
             return exceptions.get(0);
         }
     }
}


[...]
MultiExecutorShutdown multiShutdown = new MultiExecutorShutdown();
multiShutdown.shutdown(executor1);
multiShutdown.shutdown(executor2);
multiShutdown.shutdown(executor3);

Optional<InterruptedException> exception = multiShutdown.getLastException();
// alternative:
// Optional<InterruptedException> exception = multiShutdown.getFirstException();

if (exception.isPresent()) {
   throw new IllegalStateException(exception.get());
}

如果您还需要失败的执行器,您也可以修改MultiExecutorShutdown,保留一个(有序)映射ExecutorService -> Exception
您也可以将throw推入MultiExecutorShutdown本身中,使其更易用。最后,整个过程当然可以抽象出来,以便它接受一个功能调用并记录任何抛出的异常。

如果你的三个ExecutorServices中的每一个都抛出了异常,那么你将无法仅通过第一个和最后一个访问器方法来访问它们。 - harpun
@harpun 正确。实现需要根据特定用例进行调整。如果您需要所有三个异常,请添加相应的访问器方法。您甚至可以使用策略模式使解析插件化,但这可能过度设计解决方案。 - dhke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接