如果我在并行流中使用 lambda 会发生死锁,但如果我改用匿名 class 则不会发生死锁?
Deadlock happens if I use lambda in parallel stream but it doesn't happen if I use anonymous class instead?
以下代码导致死锁(在我的电脑上):
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");
}
}
但是如果我用匿名 class 替换 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");
}
}
你能解释一下这种情况吗?
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");
}
}
工作不稳定。在大多数情况下,它会挂起,但有时会成功完成:
我真的无法理解为什么这种行为不稳定。实际上我重新测试了第一个代码片段并且行为相同。所以最新的代码等于第一个。
为了了解使用了哪些线程,我添加了以下 "logging":
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
P.S。 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 body的写法是一样的Test
class,即合成法
private static int lambda$static[=10=](int n, int m) {
return n + m;
}
在第二种情况下,接口的实现驻留在 不同的 Test
class 中。因此并行流的线程不调用 Test
的静态方法,因此不依赖于 Test
初始化。
以下代码导致死锁(在我的电脑上):
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");
}
}
但是如果我用匿名 class 替换 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");
}
}
你能解释一下这种情况吗?
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");
}
}
工作不稳定。在大多数情况下,它会挂起,但有时会成功完成:
我真的无法理解为什么这种行为不稳定。实际上我重新测试了第一个代码片段并且行为相同。所以最新的代码等于第一个。
为了了解使用了哪些线程,我添加了以下 "logging":
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
P.S。 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 body的写法是一样的Test
class,即合成法
private static int lambda$static[=10=](int n, int m) {
return n + m;
}
在第二种情况下,接口的实现驻留在 不同的 Test
class 中。因此并行流的线程不调用 Test
的静态方法,因此不依赖于 Test
初始化。