【问题标题】:Deadlock happens if I use lambda in parallel stream but it doesn't happen if I use anonymous class instead? [duplicate]如果我在并行流中使用 lambda 会发生死锁,但如果我使用匿名类则不会发生死锁? [复制]
【发布时间】: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


【解决方案1】:

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

private static int lambda$static$0(int n, int m) {
    return n + m;
}

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

【讨论】:

  • @gstackoverflow 只是根据race condition 的定义。结果取决于线程计时,它们的相对执行顺序,这是未指定的。
  • @gstackoverflow 我已经在this comment 中承认了这种现象,并链接到描述类似行为的旧答案。但是为什么工作线程可以在这种情况下突然访问一个未初始化的类,当主线程已经执行了相同的代码时,这个问题在“附言”中讨论得非常广泛。另一个问题的部分。
  • @gstackoverflow 根据JVMS §5.5invokedynamic 指令不会触发类初始化,但 MethodHandle 的 first 调用会触发。关键是哪个线程先调用MethodHandle。
  • 如果您还有其他问题,请提出新问题,而不是在 cmets 中讨论。
  • @apangin invokedynamic 指令总是由主线程执行,因为它会创建 IntBinaryOperator 实例并传递给 reduce,甚至在并行缩减开始之前。但我同意这应该在专门的问答中讨论,而不是在 cmets 中讨论。
猜你喜欢
  • 1970-01-01
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
  • 2019-04-03
  • 1970-01-01
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多