带有lambda表达式的invokeAndWait在静态初始化程序中永远挂起

Mar*_*tin 5 java lambda swing

我使用invokeAndWait偶然发现了一个问题.下面的示例代码说明了该问题.任何人都可以详细说明发生了什么事吗?为什么lambda表达式挂起而匿名内部类和方法ref没有.

public class Test {
    // A normal (non-static) initializer does not have the problem
    static {
        try {
            System.out.println("initializer start");

            // --- Works
            System.out.println("\nanonymous inner-class: Print.print");
            EventQueue.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    Print.print();
                }
            });

            // --- Works
            System.out.println("\nmethod ref: Print.print");
            EventQueue.invokeAndWait(Print::print);

            // --- Hangs forever
            System.out.println("\nlambda: Print.print");
            EventQueue.invokeAndWait(() -> Print.print());

            System.out.println("\ninitializer end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Test();
    }
}
Run Code Online (Sandbox Code Playgroud)

Print类:

public class Print {
    public static void print() {
        System.out.println("Print.print");
    }
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*lex 5

这是因为 lambda 表达式(部分)被编译到 内部的方法中Test,而方法引用和匿名内部类的方法则没有。

来自Lambda 表达式的翻译

当编译器遇到 lambda 表达式时,它首先将 lambda 主体降低(脱糖)为一个方法,其参数列表和返回类型与 lambda 表达式的参数列表和返回类型匹配,可能还有一些额外的参数(对于从词法范围捕获的值,如果有的话。 )

...

方法引用的处理方式与 lambda 表达式相同,不同之处在于大多数方法引用不需要脱糖为新方法;

您可以通过查看编译类产生的字节码来验证这一点。

这很重要的原因是,当事件队列线程尝试执行通过对 lambda 主体进行脱糖创建的方法时,它会阻塞等待第一个线程完成初始化Test,并且两个线程陷入死锁。

JLS 的第 12.4 节描述了初始化过程:

类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:

  • 调用由 T 声明的静态方法。

...

如果 C 的 Class 对象指示其他线程正在对 C 进行初始化,则释放 LC 并阻塞当前线程,直到通知正在进行的初始化已完成,此时重复此步骤。

同样在JVMS 的第 5.5 节中:

在执行 getstatic、putstatic 或 invokestatic 指令时,如果尚未初始化声明已解析字段或方法的类或接口,则会对其进行初始化。

有关没有 lambda 的类似示例,请参阅此问题