I came across a strange situation where using a parallel stream with a lambda in a static initializer takes seemingly forever with no CPU utilization. Here's the code:
class Deadlock {
static {
IntStream.range(0, 10000).parallel().map(i -> i).count();
System.out.println("done");
}
public static void main(final String[] args) {}
}
This appears to be a minimum reproducing test case for this behavior. If I:
- put the block in the main method instead of a static initializer,
- remove parallelization, or
- remove the lambda,
the code instantly completes. Can anyone explain this behavior? Is it a bug or is this intended?
I am using OpenJDK version 1.8.0_66-internal.
There is an excellent explanation of this problem by Andrei Pangin, dated by 07 Apr 2015. It is available here, but it is written in Russian (I suggest to review code samples anyway - they are international). The general problem is a lock during class initialization.
Here are some quotes from the article:
According to JLS, every class has a unique initialization lock that is captured during initialization. When other thread tries to access this class during initialization, it will be blocked on the lock until initialization completes. When classes are initialized concurrently, it is possible to get a deadlock.
I wrote a simple program that calculates the sum of integers, what should it print?
Now remove
parallel()
or replace lambda withInteger::sum
call - what will change?Here we see deadlock again [there were some examples of deadlocks in class initializers previously in the article]. Because of the
parallel()
stream operations run in a separate thread pool. These threads try to execute lambda body, which is written in bytecode as aprivate static
method insideStreamSum
class. But this method can not be executed before the completion of class static initializer, which waits the results of stream completion.What is more mindblowing: this code works differently in different environments. It will work correctly on a single CPU machine and will most likely hang on a multi CPU machine. This difference comes from the Fork-Join pool implementation. You can verify it yourself changing the parameter
-Djava.util.concurrent.ForkJoinPool.common.parallelism=N
For those who are wondering where are the other threads referencing the
Deadlock
class itself, Java lambdas behave like you wrote this:With regular anonymous classes there is no deadlock:
I found a bug report of a very similar case (JDK-8143380) which was closed as "Not an Issue" by Stuart Marks:
I was able to find another bug report of that (JDK-8136753), also closed as "Not an Issue" by Stuart Marks:
Note that FindBugs has an open issue for adding a warning for this situation.