java - 为什么静态初始化程序中带有 lambda 的并行流会导致死锁?

标签 java java-8 deadlock java-stream fork-join

我遇到了一个奇怪的情况,在静态初始化程序中使用带有 lambda 的并行流似乎永远不会占用 CPU。代码如下:

class Deadlock {
    static {
        IntStream.range(0, 10000).parallel().map(i -> i).count();
        System.out.println("done");
    }
    public static void main(final String[] args) {}
}

这似乎是此行为的最小重现测试用例。如果我:

  • 将 block 放在 main 方法中,而不是静态初始化器中,
  • 移除并行化,或
  • 删除 lambda,

代码立即完成。谁能解释这种行为?这是一个错误还是有意为之?

我使用的是 OpenJDK 版本 1.8.0_66-internal。

最佳答案

我发现了一个非常相似的案例 (JDK-8143380) 的错误报告,该案例被 Stuart Marks 关闭为“不是问题”:

This is a class initialization deadlock. The test program's main thread executes the class static initializer, which sets the initialization in-progress flag for the class; this flag remains set until the static initializer completes. The static initializer executes a parallel stream, which causes lambda expressions to be evaluated in other threads. Those threads block waiting for the class to complete initialization. However, the main thread is blocked waiting for the parallel tasks to complete, resulting in deadlock.

The test program should be changed to move the parallel stream logic outside of the class static initializer. Closing as Not an Issue.


我找到了另一个错误报告 (JDK-8136753),同样被 Stuart Marks 关闭为“不是问题”:

This is a deadlock that is occurring because the Fruit enum's static initializer is interacting badly with class initialization.

See the Java Language Specification, section 12.4.2 for details on class initialization.

http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2

Briefly, what's happening is as follows.

  1. The main thread references the Fruit class and starts the initialization process. This sets the initialization in-progress flag and runs the static initializer on the main thread.
  2. The static initializer runs some code in another thread and waits for it to finish. This example uses parallel streams, but this has nothing to do with streams per se. Executing code in another thread by any means, and waiting for that code to finish, will have the same effect.
  3. The code in the other thread references the Fruit class, which checks the initialization in-progress flag. This causes the other thread to block until the flag is cleared. (See step 2 of JLS 12.4.2.)
  4. The main thread is blocked waiting for the other thread to terminate, so the static initializer never completes. Since the initialization in-progress flag isn't cleared until after the static initializer completes, the threads are deadlocked.

To avoid this problem, make sure that a class's static initialization completes quickly, without causing other threads to execute code that requires this class to have completed initialization.

Closing as Not an Issue.


请注意 FindBugs has an open issue for adding a warning对于这种情况。

关于java - 为什么静态初始化程序中带有 lambda 的并行流会导致死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34820066/

相关文章:

java - Android FrameLayout 中的 ProgressBar 永远不可见

java - 使用 Streams 进行嵌套循环以实现最佳性能

java - JDK 1.7 允许自定义 taglet 的名称*以点开头*。 JDK 1.8 禁止它?

等待已授予的锁的 Mysql 事务。这导致死锁

java - Android 是否保留 View 更改历史记录?

java - 兴趣点/Excel : applying formulas in a "relative" way

Java - 使用流 + lambdas 的多个集合的交集

java - 如何在 Java 中防止这种简单的死锁?

c - 使用两个缓冲区和两个互斥锁的线程同步: C

java - 如何使用 docx4j api 设置行距?