【问题标题】:What can cause ExecutorService.invokeAll() to throw an InterruptedException?什么会导致 ExecutorService.invokeAll() 抛出 InterruptedException?
【发布时间】:2017-08-30 09:37:50
【问题描述】:

javadoc 说 invokeAll(Collection<> callables) throws

InterruptedException - 如果在等待时被中断,则取消未完成的任务

但没有关于为什么呼叫会被中断的文档。我的程序会这样做——但很少发生,我无法编写会导致它发生的测试。

我正在使用没有超时的单参数方法。

public class ParallelUtil<T> {

    public interface Task<T> {
        public void execute(T data) throws Exception;
    }

    public void execute(final Task<T> task, Collection<T> targets) {
        ExecutorService executor = null;
        try {
            executor = Executors.newFixedThreadPool(20);
            execute(task, targets, executor);
        } finally {
            if (executor != null) {
                try {
                    executor.shutdown();
                } catch (Exception e) {}
            }
        }
    }

    private void execute(final Task<T> task, Collection<T> targets, ExecutorService executor) {
        List<Callable<Void>> tasks = new ArrayList<>();
        ...
        try {
            executor.invokeAll(tasks);
        } catch (Exception e) {
            // Here we get InterruptedException - for no reason?
            // With some of the tasks left undone!
        }
    }
}

java.util.concurrent.FutureTask.awaitDone(FutureTask.java:40‌​0) 抛出(至少在某些情况下)InterruptedException

可以同时运行许多ParallelUtils(从不同的线程启动),但正如您所见,每个调用都会创建自己的ExecutorService,因此它们不应相互混淆。

(作为一个附带问题,我可以为所有调用使用共享池,而不会使 invokeAll 调用相互混淆吗?)

【问题讨论】:

  • } catch (Exception e) {} 你不会真的吞下去吧?
  • 我会用一个工具来装饰任务,以捕获 InterruptedExceptions 以找出哪些是被中断的。我怀疑中断发生在一个(或多个)任务的执行代码中。
  • @Fildor 不,我不会吞下它们,我会用一些调试数据在那里抛出另一个异常。我不完全确定装饰器应该是什么样子——中断发生在 invokeAll() 的内部逻辑中(更准确地说是在 java.util.concurrent.FutureTask.awaitDone(FutureTask.java:400) 中——我应该添加这个位问题的信息)
  • 回到这个问题,原因如下:我的线程是由线程中运行的东西创建的。创建线程被中断(我知道会发生这种情况)-> 中断创建线程导致被中断线程创建的线程中断(没有完成它们的工作)。修复:创建线程的创建者中的一个标志,确保正在运行的线程不会被中断。

标签: java concurrency executorservice


【解决方案1】:

可以同时运行许多这样的 ParallelUtils(从不同的线程启动)

显然,这些线程中至少有一个被直接通过Thread.interrupt() 或通过例如间接中断。 Future.cancel(true)ExecutorService.shutdownNow()

重现此中断的示例:

class Sleep implements ParallelUtil.Task<Integer> {
  @Override
  public void execute(Integer data) throws Exception {
    Thread.sleep(Long.MAX_VALUE);
  }
}

class InterruptInvokeAll {

  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    executorService.submit(
        () -> {
          ParallelUtil<Integer> parallelUtil = new ParallelUtil<>();
          parallelUtil.execute(new Sleep(), Arrays.asList(1));
        });

    executorService.shutdownNow(); // indirectly interrupts thread that calls executor.invokeAll
   }    
}

【讨论】:

  • 启动 ParallelUtils 的线程确实被中断了(以便在新工作到达时将它们从睡眠中唤醒)——但我假设这只会将它们从睡眠中唤醒,并且如果线程正在工作则没有效果.如果他们正在工作(他们在创建 ParallelUtil 时就是这样),“父”线程上的中断会导致 invokeAll() 中断吗?
  • @EskoPiirainen 不确定“父”线程是什么意思。如果调用invokeAll()(或任何其他interruptible方法,例如Thread.sleep)的线程被中断,则方法调用将被InterruptedException中止(并且线程的中断标志被清除) .
  • 这个小测试bitbucket.org/snippets/epiirainen/ep6g58 向我表明,当线程正在工作时,它只会在它开始休眠后才会被中断。输出为:0 正在工作 515 现在我将中断 5429 停止工作 5429 现在我从睡眠中被打断了
  • (我在您回复后几秒钟就发布了上述内容。)谢谢,我想我们现在已经接近问题了。我会写一个测试来确认。
  • 确实如此。 bitbucket.org/snippets/epiirainen/g9y9Xg -> 0 正在工作 0 使用 a 15 使用 c 15 使用 b 109 现在我将中断 -> java.lang.RuntimeException: 109 现在它发生了 -> java.util.concurrent 的 java.lang.InterruptedException .FutureTask.awaitDone(FutureTask.java:404) 。如果父母正在工作,我必须确保“叫醒电话”不会打断“父母”。
【解决方案2】:

什么会导致ExecutorService.invokeAll() 抛出InterruptedException

查看shutdownNow()的javadoc:

除了尽力停止处理正在执行的任务之外,没有任何保证。 例如,典型的实现将通过Thread.interrupt() 取消,因此任何未能响应中断的任务可能永远不会终止。

由于invokeAll 一直等到所有任务都完成,所以从另一个线程调用shutdownNow()invokeAll 调用可能被中断的一种方式。


但是,在代码中正如您向我们展示的那样,没有对shutdownNow() 的调用,也没有明显的方式让执行程序服务对象泄漏到另一个线程。

您的代码(或其他一些库代码)中的某些内容也可能正在调用Thread.interrupt。例如,其中一项任务可能是对自己执行此操作。

【讨论】:

  • 在 OP 的代码 sn-p 中,ExecutorService 是 public void execute(final Task&lt;T&gt; task, Collection&lt;T&gt; targets) 方法的本地。所以我认为这(shutdownNow 被调用)不太可能。
  • 您假设 OP 的实际代码反映了此代码。如果这是真的,那么你是对的。打电话给shutdownNow() 是不可能的。但是.....我很确定这段代码已经被简化/清理了。
  • 是的,我的反对意见是基于这个假设。这就是为什么我在“概率”中表达我的怀疑:) 你的第二点对我来说似乎更有可能。但我猜只有 OP 可以验证这一点。
  • 感谢您的回复!这些 executor 没有调用 shutdownNow(),但是 shutdownNow() 在系统的其他部分为完全不同的 executor 调用了两次。我假设执行程序是线程安全的..
  • (我没有直接回答的原因是,一般来说不可能说一个类是否完全线程安全。线程安全通常取决于你如何使用一个类及其方法。什么您需要做的是检查您是如何调用方法的,然后查看源代码以查看操作是否以您使用它们的方式是线程安全的。另一点是ExecutorService 是一个接口。它是重要的实现......对于线程安全。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-24
  • 2019-04-03
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多