前一段时间遇到一段很诡异的问题,模拟一下问题出现的现场。
public class StreamTest {
static {
int sum = IntStream.range(0, 100)
.parallel()
.map(i -> i)
.sum();
System.out.println("sum = " + sum);
}
public static void main(String[] args) {
}
}
上述代码在运行的时候会产生死锁,代码并不会执行静态代码块的输出,也就说阻塞到Stream计算这个地方了。如果将Stream改为普通的串行流的话,代码则可以正常运行。那么问题就出现在了并行上面。
Stream的并行操作是通过ForkJoinPool来实现的,ForkJoinPool是一种特殊的线程池,通过对任务进行分割计算,结果合并来充分利用cpu多核性能,是一种特殊的线程池。Stream有一个最坑的地方就是,所有的并行操作都是用的同一个ForkJoinPool池,也就是ForkJoinPool.commPool()返回的这个池,可以通过下述代码来验证:
public static void main(String[] args) {
IntStream.range(0,10).parallel().forEach(i-> System.out.println(Thread.currentThread().getName()));
}
所以出现这个问题的关键不是在于Stream,而是在于多线程和类初始化。经我测试,以下代码仍然会产生同样的问题:
public class StreamTest {
static {
Thread thread = new Thread(() -> {
try {
Thread.sleep(500);
System.out.println("StreamTest.static initializer");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
}
}
经过查证发现类在未完成初始化时,静态代码块的其他线程操作会等待当前类初始化完成,然而当前类初始化又在等待其他线程操作的完成,于是产生死锁。
我的建议是不要在静态代码块中做任何线程相关的操作,经过我后续的查找,在Stack Overflow上面发现一个相同问题的解答,完美的解释了这个问题出现的原因: