如果我在并行使用lambda则会发生死锁,但是如果我使用匿名类却不会发生死锁?
问题内容:
以下代码导致死锁(在我的电脑上):
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
类中编写的,即合成方法
private static int lambda$static$0(int n, int m) {
return n + m;
}
在第二种情况下,接口的实现位于不同的 Test$1
类中。因此,并行流的线程不会调用的静态方法,Test
因此不依赖于Test
初始化。