Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/366.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么在静态初始值设定项中使用lambda的并行流会导致死锁?_Java_Java 8_Deadlock_Java Stream_Fork Join - Fatal编程技术网

Java 为什么在静态初始值设定项中使用lambda的并行流会导致死锁?

Java 为什么在静态初始值设定项中使用lambda的并行流会导致死锁?,java,java-8,deadlock,java-stream,fork-join,Java,Java 8,Deadlock,Java Stream,Fork Join,我遇到了一个奇怪的情况,在静态初始值设定项中使用带有lambda的并行流似乎要花费很长时间,而且没有CPU利用率。代码如下: class Deadlock { static { IntStream.range(0, 10000).parallel().map(i -> i).count(); System.out.println("done"); } public static void main(final String[] arg

我遇到了一个奇怪的情况,在静态初始值设定项中使用带有lambda的并行流似乎要花费很长时间,而且没有CPU利用率。代码如下:

class Deadlock {
    static {
        IntStream.range(0, 10000).parallel().map(i -> i).count();
        System.out.println("done");
    }
    public static void main(final String[] args) {}
}
这似乎是此行为的最小复制测试用例。如果我:

  • 将块放在main方法中,而不是静态初始值设定项中
  • 删除并行化,或
  • 拆下lambda
代码立即完成。有人能解释这种行为吗?这是一个bug还是有意的


我正在使用OpenJDK版本1.8.0_66-internal。

我发现了一个非常类似的案例()的错误报告,Stuart Marks以“非问题”结束了该案例:

这是一个类初始化死锁。测试程序的主线程执行类静态初始化器,该初始化器设置类的初始化进行中标志;此标志保持设置状态,直到静态初始值设定项完成。静态初始值设定项执行并行流,这将导致在其他线程中计算lambda表达式。这些线程阻塞等待类完成初始化。但是,主线程在等待并行任务完成时被阻塞,导致死锁

应更改测试程序,以将并行流逻辑移到类静态初始值设定项之外。关闭不是一个问题


我找到了另一个关于该()的bug报告,Stuart Marks也以“非问题”结尾:

这是一个死锁,因为Fruit枚举的静态初始值设定项与类初始化交互不好

有关类初始化的详细信息,请参阅Java语言规范第12.4.2节

简而言之,发生的情况如下

  • 主线程引用水果类并启动初始化过程。这将设置initialization in progress标志并在主线程上运行静态初始值设定项
  • 静态初始值设定项在另一个线程中运行一些代码,并等待它完成。本例使用并行流,但这与流本身无关。以任何方式在另一个线程中执行代码,并等待该代码完成,都将具有相同的效果
  • 另一个线程中的代码引用了水果类,该类检查初始化进行中标志。这会导致另一个线程阻塞,直到清除该标志为止。(参见JLS 12.4.2中的步骤2。)
  • 主线程在等待另一个线程终止时被阻塞,因此静态初始值设定项永远不会完成。由于初始化进行中标志直到静态初始值设定项完成后才会清除,因此线程处于死锁状态
  • 要避免此问题,请确保类的静态初始化快速完成,而不会导致其他线程执行要求此类完成初始化的代码

    关闭不是一个问题



    请注意,对于这种情况。

    对于那些想知道引用
    Deadlock
    类本身的其他线程在哪里的人,Java lambda的行为与您编写的类似:

    public class Deadlock {
        public static int lambda1(int i) {
            return i;
        }
        static {
            IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {
                @Override
                public int applyAsInt(int operand) {
                    return lambda1(operand);
                }
            }).count();
            System.out.println("done");
        }
        public static void main(final String[] args) {}
    }
    
    对于常规匿名类,没有死锁:

    public class Deadlock {
        static {
            IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {
                @Override
                public int applyAsInt(int operand) {
                    return operand;
                }
            }).count();
            System.out.println("done");
        }
        public static void main(final String[] args) {}
    }
    

    2015年4月7日,对该问题有一个很好的解释。它是可用的,但它是用俄语编写的(我建议无论如何都要检查代码示例——它们是国际性的)。一般的问题是类初始化期间的锁

    以下是文章中的一些引文:


    根据,每个类都有一个在初始化期间捕获的唯一初始化锁。当其他线程在初始化期间尝试访问此类时,它将被锁定,直到初始化完成。当类同时初始化时,可能会出现死锁

    我写了一个简单的程序来计算整数的和,它应该打印什么

    public class StreamSum {
        static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();
    
        public static void main(String[] args) {
            System.out.println(SUM);
        }
    } 
    
    现在删除
    parallel()
    或将lambda替换为
    Integer::sum
    调用-会有什么变化

    这里我们再次看到死锁[在本文前面的类初始值设定项中有一些死锁示例]。由于
    parallel()
    流操作在单独的线程池中运行。这些线程尝试执行lambda body,它作为
    StreamSum
    类中的
    private static
    方法以字节码编写。但是在类静态初始值设定项完成之前不能执行此方法,该初始化项将等待流完成的结果


    更令人兴奋的是:这段代码在不同的环境中工作方式不同。它将在单CPU机器上正常工作,并且很可能挂起在多CPU机器上。这种差异来自Fork-Join池实现。您可以自己更改参数
    -Djava.util.concurrent.ForkJoinPool.common.parallelism=N

    ,范围为(0,1),程序正常终止。具有(0,2)或更高的挂起。类似问题:实际上是完全相同的问题/问题,只是具有不同的API。您正在尝试在后台线程中使用类,当您尚未完成类的初始化,因此无法在后台线程中使用时。@Solomonoff的cret as
    i->i
    不是方法引用,而是在死锁类中实现的
    静态方法。如果用
    Function.identity()
    替换
    i->i
    ,则此代码应该可以。@Solomonoff's确认这是一个实现选择。lambda中的代码必须放在某个地方。Javac将其编译成包含类中的静态方法(类似于本例中的
    lambda1
    i)。将每个lambda放在自己的类中会花费相当大的成本。@StuartMarks如果lambda创建了一个实现函数接口的类,那么将lambda的实现放在自己的类中不是同样有效吗