Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

invokeAndWait with lambda expression hangs forever in static initializer

Tags:

java

lambda

swing

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");
    }
}
like image 703
Martin Avatar asked Dec 11 '15 11:12

Martin


2 Answers

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.

like image 65
Alex - GlassEditor.com Avatar answered Oct 24 '22 16:10

Alex - GlassEditor.com


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


  1. by using standard invokeLateris 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)

  1. by using standard invokeLater and JFrame , everything is o.k., all three threads ends with JFrame on the screen, success (this is correct output, awating that)

enter image description here .

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

  1. by using standard 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

  1. by using invokeAndWait and JFrame

never shows jframe initialized from lambda expresion, it must be killed from IDE

enter image description here .

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);
    }
}

.

like image 31
mKorbel Avatar answered Oct 24 '22 15:10

mKorbel