以下代码导致死锁(在我的电脑上):
public class Test { static { final int SUM = IntStream.range(0, 100) .parallel() .reduce((n, m) -> n + m) .getAsInt(); } public static void main(String[] args) { System.out.println("Finished"); } }
但是,如果我将reducelambda参数替换为匿名类,则不会导致死锁:
public class Test { static { final int SUM = IntStream.range(0, 100) .parallel() .reduce(new IntBinaryOperator() { @Override public int applyAsInt(int n, int m) { return n + m; } }) .getAsInt(); } public static void main(String[] args) { System.out.println("Finished"); } }
你能解释一下这种情况吗?
P.S.
我发现该代码(与之前的代码有些不同):
public class Test { static { final int SUM = IntStream.range(0, 100) .parallel() .reduce(new IntBinaryOperator() { @Override public int applyAsInt(int n, int m) { return sum(n, m); } }) .getAsInt(); } private static int sum(int n, int m) { return n + m; } public static void main(String[] args) { System.out.println("Finished"); } }
工作不稳定。在大多数情况下,它挂起了,但是有时它成功完成了:
在此处输入图片说明
我真的不明白为什么这种行为不稳定。实际上,我重新测试了第一个代码段,并且行为相同。因此,最新的代码等于第一个。
为了了解使用了哪些线程,我在“日志记录”后面添加了以下内容:
public class Test { static { final int SUM = IntStream.range(0, 100) .parallel() .reduce((n, m) -> { System.out.println(Thread.currentThread().getName()); return (n + m); }) .getAsInt(); } public static void main(String[] args) { System.out.println("Finished"); } }
对于应用程序成功完成的情况,我会看到以下日志:
main main main main ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 main ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 main main main main main main main main main main main main main main main main main ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 main main main main main main main main main main main main main main main main main main main main main main main main main main main ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 ForkJoinPool.commonPool-worker-1 Finished PS 2
我不明白reduce是足够复杂的操作。我找到了一个更简单的示例来显示该问题:
public class Test { static { System.out.println("static initializer: " + Thread.currentThread().getName()); final long SUM = IntStream.range(0, 2) .parallel() .mapToObj(i -> { System.out.println("map: " + Thread.currentThread().getName() + " " + i); return i; }) .count(); } public static void main(String[] args) { System.out.println("Finished"); } }
对于幸福的情况(罕见的情况),我看到以下输出:
static initializer: main map: main 1 map: main 0 Finished
扩展流范围的幸福案例示例:
static initializer: main map: main 2 map: main 3 map: ForkJoinPool.commonPool-worker-2 4 map: ForkJoinPool.commonPool-worker-1 1 map: ForkJoinPool.commonPool-worker-3 0 Finished
导致死锁的案例示例:
static initializer: main map: main 1
它还会导致死锁,但不会导致每次启动。
区别在于lambda主体是在同一Test类中编写的,即合成方法
Test
private static int lambda$static$0(int n, int m) { return n + m; }
在第二种情况下,接口的实现位于不同的 Test$1类中。因此,并行流的线程不会调用的静态方法,Test因此不依赖于Test初始化。
Test$1