我使用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)
这是因为 lambda 表达式(部分)被编译到 内部的方法中Test
,而方法引用和匿名内部类的方法则没有。
当编译器遇到 lambda 表达式时,它首先将 lambda 主体降低(脱糖)为一个方法,其参数列表和返回类型与 lambda 表达式的参数列表和返回类型匹配,可能还有一些额外的参数(对于从词法范围捕获的值,如果有的话。 )
...
方法引用的处理方式与 lambda 表达式相同,不同之处在于大多数方法引用不需要脱糖为新方法;
您可以通过查看编译类产生的字节码来验证这一点。
这很重要的原因是,当事件队列线程尝试执行通过对 lambda 主体进行脱糖创建的方法时,它会阻塞等待第一个线程完成初始化Test
,并且两个线程陷入死锁。
JLS 的第 12.4 节描述了初始化过程:
类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:
- 调用由 T 声明的静态方法。
...
如果 C 的 Class 对象指示其他线程正在对 C 进行初始化,则释放 LC 并阻塞当前线程,直到通知正在进行的初始化已完成,此时重复此步骤。
同样在JVMS 的第 5.5 节中:
在执行 getstatic、putstatic 或 invokestatic 指令时,如果尚未初始化声明已解析字段或方法的类或接口,则会对其进行初始化。
有关没有 lambda 的类似示例,请参阅此问题。