日期为2015年4月7日的Andrei Pangin对这个问题有很好的解释。可以在此处找到,但是它是用俄语编写的(无论如何,我建议您检查代码示例-它们是国际性的)。通常的问题是在类初始化期间加锁。
以下是文章中的一些引文:
根据JLS,每个类都有一个唯一的初始化锁,该锁在初始化期间捕获。当其他线程在初始化期间尝试访问此类时,它将在锁上被阻止,直到初始化完成。同时初始化类时,可能会出现死锁。
我写了一个简单的程序来计算整数之和,应该打印什么?
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);
}
}
现在,将parallel()
lambda删除或替换为call-Integer::sum
将会发生什么变化?
在这里,我们再次看到了死锁(本文前面的类初始化程序中有死锁的一些示例)。由于parallel()
流操作在单独的线程池中运行。这些线程尝试执行lambda主体,该主体以字节码形式编写为类中的一种private static
方法StreamSum
。但是,该方法不能在类静态初始化程序完成之前执行,后者将等待流完成的结果。
更令人振奋的是:此代码在不同环境中的工作方式有所不同。它可以在单CPU计算机上正常运行,并且很可能在多CPU计算机上挂起。这种差异来自Fork-Join池的实现。您可以自己更改参数来验证-Djava.util.concurrent.ForkJoinPool.common.parallelism=N