Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does parallel stream with lambda in static initializer cause a deadlock?

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.

like image 875
Reinstate Monica Avatar asked Jan 15 '16 21:01

Reinstate Monica


People also ask

When we should not use parallel stream?

Similarly, don't use parallel if the stream is ordered and has much more elements than you want to process, e.g. This may run much longer because the parallel threads may work on plenty of number ranges instead of the crucial one 0-100, causing this to take very long time.

Are parallel streams thread safe?

Parallel streams provide the capability of parallel processing over collections that are not thread-safe. It is although required that one does not modify the collection during the parallel processing.

Is parallel stream blocking?

An operation on a ParallelStream is still blocking and will wait for all the threads it spawned to finish. These threads are executed asynchronously (they don't wait for a previous one to finish), but that doesn't mean your whole code starts behaving asynchronously !

Does parallel stream improve performance?

The Stream API makes it possible to execute a sequential stream in parallel without rewriting the code. The primary reason for using parallel streams is to improve performance while at the same time ensuring that the results obtained are the same, or at least compatible, regardless of the mode of execution.


2 Answers

I found a bug report of a very similar case (JDK-8143380) which was closed as "Not an Issue" by 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.


I was able to find another bug report of that (JDK-8136753), also closed as "Not an Issue" by 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.


Note that FindBugs has an open issue for adding a warning for this situation.

like image 123
Tunaki Avatar answered Sep 20 '22 02:09

Tunaki


For those who are wondering where are the other threads referencing the Deadlock class itself, Java lambdas behave like you wrote this:

public class Deadlock {     public static int lambda1(int i) {         return i;     }     static {         IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {             @Override             public int applyAsInt(int operand) {                 return lambda1(operand);             }         }).count();         System.out.println("done");     }     public static void main(final String[] args) {} } 

With regular anonymous classes there is no deadlock:

public class Deadlock {     static {         IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {             @Override             public int applyAsInt(int operand) {                 return operand;             }         }).count();         System.out.println("done");     }     public static void main(final String[] args) {} } 
like image 35
Tamas Hegedus Avatar answered Sep 21 '22 02:09

Tamas Hegedus