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?
public class StreamSum {
static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();
public static void main(String[] args) {
System.out.println(SUM);
}
}
Now remove parallel()
or replace lambda with Integer::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 a private static
method inside StreamSum
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
i -> i
is not a method reference it is astatic method
implemented in the Deadlock class. If replacei -> i
withFunction.identity()
this code should be fine. – Monomeri -> i
becomes an implementation of a functional interface that behaves as a real object. Doesn't that mean it needs to be invoked polymorphically and if so, how can it simply be a static method? Doesn't it at least need to have a method that implements the method of the functional interface which in the current implementation delegates to the static method? Or is there some magic happening behind the scenes? – Siward