【问题标题】:ExecutorService invokeAll deadlockExecutorService invokeAll 死锁
【发布时间】:2015-01-13 18:44:58
【问题描述】:

在为ExecutorService invokeAll 编写骨架程序时,我遇到了一个有趣的场景,它似乎造成了死锁。无法弄清楚为什么会这样。

这是实例化3个任务并调用invokeAll()的程序

        int poolSize = Runtime.getRuntime().availableProcessors();
        ExecutorService pool = Executors.newFixedThreadPool(poolSize);
        Set<Callable<Object>> tasksSet = new HashSet<>();
        tasksSet.add(new Task1());
        tasksSet.add(new Task2());
        tasksSet.add(new Task3());

        List<Future<Object>> resultSet = pool.invokeAll(tasksSet);
        for (Future<Object> future : resultSet) {
            Object result;
            try {
                   result = future.get(5, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
                Logger.getLogger(CallableDemo.class.getName()).log(Level.SEVERE, null, ex);
            } catch (ExecutionException ex) {
                ex.printStackTrace();
                Logger.getLogger(CallableDemo.class.getName()).log(Level.SEVERE, null, ex);
            } catch (TimeoutException ex) {
                ex.printStackTrace();
                Logger.getLogger(CallableDemo.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        pool.shutdown();

还有Task1代码:

public class Task1 implements Callable<Object> {

    @Override
    public Object call() throws Exception {
        long val = 0;
        for (long i = 0; i < Long.MAX_VALUE - 5000; i++) {
            val += i;
        }
        return "Sucessfull Task1 object...";
    }

}

Task2Task3 代码也相同,除了这两个类在循环检查中使用 Integer.MAX_VALUE

当我运行这个程序时,它永远卡住了,有趣的是,其他两个任务也没有运行。我的机器是8核处理器。有什么想法为什么会出现这种行为?

如果我将 Long.MAX_VALUE 更改为 Integer.MAX_VALUE,一切正常。

另一个有趣的观察是:

如果submit() 单独调用这些任务,而不是调用invokeAll(),除Task1(具有Long.MAX_VALUE)之外的另外两个任务按时完成。

【问题讨论】:

  • 您有没有机会为您的程序获得线程转储?这将是确定是否确实存在死锁的最佳方法。
  • 它总是可重现的,因此获取线程转储可能不是问题。但我在这里苦苦挣扎的是,在多核机器上,任务处理不应该是并发的吗?

标签: java-7 executorservice java.util.concurrent


【解决方案1】:

您观察到的这种行为确实是预期的行为。考虑以下几点:

1) Task3 最多为 Integer.MAX_VALUE - 5000,而 Task1 和 Task2 最多为 Long.MAX_VALUE - 5000。在所有条件相同的情况下,这意味着 Task1 和 Task2 的持续时间是 Task3 的 2^32 倍,即超过 40 亿倍

2) 如AbstractExecutorService.invokeAll() 的 Javadoc 中所指定(强调我的):

执行给定的任务,返回持有它们的 Futures 列表 状态和结果全部完成后

因此,正在发生的事情是 Task3 完成得非常快,但其他两个任务将运行数天或更可能数月。由于invokeAll() 仅在所有任务完成后返回,因此您实际上永远不会看到它返回。

您可以通过在 for 循环之后立即在 Task3 中打印跟踪来轻松验证这一点,例如:

public static class Task3 implements Callable<Object> {
  @Override
  public Object call() throws Exception {
    long start = System.nanoTime();
    long val = 0;
    for (long i = 0; i < Integer.MAX_VALUE - 5000; i++) {
      val += 1;
    }
    double elapsed = (System.nanoTime() - start) / 1_000_000d;
    System.out.printf("Task3 terminated in %.3f ms%n", elapsed);
    return "Sucessfull Task3 object...";
  }
}

在我的 8 核机器上,它给了我:Task3 terminated in 568.938 ms。打个比方,即使 Task3 只需要 1 毫秒完成,其他两个任务仍然需要 46 天以上才能完成。

顺便说一句,我不确定您想在for 循环中使用val += i 完成什么,因为这会导致许多整数溢出。

【讨论】:

  • 感谢您的宝贵时间!我在文档中查看了“全部完成时”,这迫使我认为这是出乎意料的行为。现在它是有道理的。这是虚拟程序,val +=i 等,就在那里,不重要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-17
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多