I stumbled across an issue using invokeAndWait. The example code below illustrates the issue. Can anyone elaborate on whats happening? Why the lambda expression hangs while the anonymous inner class and method ref doesn't.
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();
}
}
The Print class:
public class Print {
public static void print() {
System.out.println("Print.print");
}
}
This is because the lambda expression is (in part) compiled into a method inside Test
, while the method reference and the method of the anonymous inner class are not.
From Translation of Lambda Expressions:
When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression, possibly with some additional arguments (for values captured from the lexical scope, if any.)
...
Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method;
You can verify this by looking at the bytecode produced from compiling your classes.
The reason this matters is that when the event queue thread attempts to execute the method created by desugaring the lambda body it blocks waiting for the first thread to finish intializing Test
, and the two threads become deadlocked.
The initialization procedure is described in section 12.4 of the JLS:
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:
- A static method declared by T is invoked.
...
If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this step.
Also in section 5.5 of the JVMS:
Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.
See this question for a similar example without lambdas.
Note this answer is just and about How really the Initial Thread works with static methods in Java8, it doesn't account for the behavior at runtime
simple testing by using invokeLater quite agree with comments here, for better understanding
seems like as bug or feature in JDK8 and Swing APIs (??the same as bug or feature about removing all Thread Safe methods in JDK7 )
martin wrote - I know what I'm doing - sometimes invokeAndWait is necessary and until this I've never had issues with it. - never seen this situations, never needed to use invokeAndWait in todays Java versions Java 1.6 and never versions
AWT Event queue required initializations of AWT/Swing JComponents
APIs for AWT/Swing GUI doesn't guarantee ordering of events
invokeLater
is output without (J)Components, everything is o.k., all three threads ends with success.
initializer start
- anonymous inner-class: Print.print
- method ref: Print.print
* Print.print called from - anonymous inner-class
- lambda: Print.print
** ** Print.print called from - method ref
- initializer end
* Print.print called from - lambda
BUILD SUCCESSFUL (total time: 1 second)
invokeLater
and JFrame , everything is o.k., all three threads ends with JFrame on the screen, success (this is correct output, awating that).
initializer start
- anonymous inner-class: Print.print
- method ref: Print.print
- lambda: Print.print
- initializer end
* Print.print called from - anonymous inner-class
** ** Print.print called from - method ref
* Print.print called from - lambda
invokeAndWait
without (J)Components, never ends from lambda expresion, it must be killed from IDE .
initializer start
- anonymous inner-class: Print.print
* Print.print called from - anonymous inner-class
- method ref: Print.print
** ** Print.print called from - method ref
- lambda: Print.print
invokeAndWait
and JFrame never shows jframe initialized from lambda expresion, it must be killed from IDE
.
run:
initializer start
- anonymous inner-class: Print.print
* Print.print called from - anonymous inner-class
- method ref: Print.print
** ** Print.print called from - method ref
- lambda: Print.print
BUILD STOPPED (total time: 3 minutes 40 seconds)
.
from code
public class Test {
// A normal (non-static) initializer does not have the problem
static {
try {
System.out.println("initializer start");
// --- Works
System.out.println("\n - anonymous inner-class: Print.print");
EventQueue.invokeLater/*EventQueue.invokeAndWait*/(new Runnable() {
@Override
public void run() {
Print.print("anonymous inner-class");
}
});
// --- Works
System.out.println("\n - method ref: Print.print");
EventQueue.invokeLater/*EventQueue.invokeAndWait*/(Print::print);
// --- Hangs forever
System.out.println("\n - lambda: Print.print");
EventQueue.invokeLater/*EventQueue.invokeAndWait*/(() -> Print.print("lambda"));
System.out.println("\n - initializer end");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Test();
}
}
and
import javax.swing.JFrame;
public class Print {
public static final void print() {
/*
JFrame frame = new JFrame();
frame.setTitle("called from - method ref");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);*/
System.out.println(" ** ** Print.print called from - method ref");
}
public static final void print(String str) {
/*
JFrame frame = new JFrame();
frame.setTitle("called from - " + str);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);*/
System.out.println(" * Print.print called from - " + str);
}
}
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With