如果我在并行流中使用lambda会发生死锁但是如果我使用匿名类而不会发生这种情况?

gst*_*low 2 java deadlock initialization forkjoinpool java-stream

以下代码导致死锁(在我的电脑上):

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我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");
    }
}
Run Code Online (Sandbox Code Playgroud)

你能解释一下这种情况吗?

PS

我发现代码(与以前有点不同):

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

工作不稳定.在大多数情况下,它会挂起,但有时它会成功完成:

在此输入图像描述

我真的无法理解为什么这种行为不稳定.实际上我重新测试第一个代码片段并且行为相同.所以最新的代码等于第一个.

为了理解使用了哪些线程,我在"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");
    }
}
Run Code Online (Sandbox Code Playgroud)

对于应用程序成功完成的情况,我看到以下日志:

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
Run Code Online (Sandbox Code Playgroud)

PS 2

我认为减少是足够复杂的操作.我找到了一个更简单的例子来证明这个问题:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

对于快乐的情况(罕见的情况)我看到以下输出:

static initializer: main
map: main 1
map: main 0
Finished
Run Code Online (Sandbox Code Playgroud)

扩展流范围的快乐案例示例:

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
Run Code Online (Sandbox Code Playgroud)

导致死锁的案例示例:

static initializer: main
map: main 1
Run Code Online (Sandbox Code Playgroud)

它也会导致死锁,但不会导致每次启动.

apa*_*gin 5

区别在于lambda body写在同一个Test类中,即合成方法

private static int lambda$static$0(int n, int m) {
    return n + m;
}
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,接口的实现驻留在不同的 Test$1类中.因此,并行流的线程不会调用静态方法,Test因此不依赖于Test初始化.

  • 如果您还有其他问题,请提出新问题,而不是在评论中讨论. (2认同)
  • @apangin`invokedynamic`指令总是由主线程执行,因为它将创建`IntBinaryOperator`实例,该实例在并行缩减甚至开始之前传递给`reduce`.但我同意这应该在专门的问答中讨论而不是评论. (2认同)