【发布时间】:2018-12-10 12:58:21
【问题描述】:
以下代码导致死锁(在我的电脑上):
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");
}
}
但如果我用匿名类替换 reduce lambda 参数,它不会导致死锁:
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");
}
}
你能解释一下这种情况吗?
附言
我发现了那个代码(和以前的有点不同):
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");
}
}
工作不稳定。在大多数情况下它会挂起,但有时它会成功完成:
我真的无法理解为什么这种行为不稳定。实际上我重新测试了第一个代码 sn-p 并且行为相同。所以最新的代码等于第一个。
为了了解使用了哪些线程,我在“日志记录”之后添加了:
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
附注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
它也会导致死锁,但不是每次启动。
【问题讨论】:
-
@Aomine,你在那里看到我的问题的答案吗?
-
那里有一秒钟的疑问,但是是的,它是完全相同的。在问题中,他说如果他删除了 lambda,那么代码就可以工作,所以这与您描述的问题完全相同。
-
@gstackoverflow 它在那个副本中是明确的——一旦你在静态初始化器中使用多线程代码,你就会陷入困境;究竟发生在你身上。顺便说一句,没有“解决方法”——只是不要使用
parallel进行流操作,无论如何它不会提高你的性能...... -
@gstackoverflow 那里 是 一个解释这一点的答案,不是吗?
-
@gstackoverflow 所有信息都在那里。我们不需要牵着你的手穿过它。我不敢相信您在这段时间内可能已经彻底阅读了每一个答案和两个 JDK 错误票。
标签: java initialization java-stream deadlock forkjoinpool