Search code examples
javamultithreadingswingevent-dispatch-threadinvokelater

If EDT is a separate thread, why does invokeLater wait for the main thread to finish in this example?


So if Event Dispatch Thread is a separate thread from the main thread, that makes me think the next code would output

Before
Runnable
true
After

But when i run it, it's as if the EDT waits for the main thread to finish before running the chunk of code inside the invokeLater(..) method. And the output is:

Before
After
Runnable
true

The code:

public class Main {
    public static void main(String[] args) {
        System.out.println("Before");
        SwingUtilities.invokeLater(() -> {
            System.out.println("Runnable");
            System.out.println(SwingUtilities.isEventDispatchThread());
        });

        System.out.println("After");
    }
}

However, if i replace invokeLater(..) with invokeAndWait(..), then i get

Before
Runnable
true
After

This makes me think the EDT is not really a separate thread, or at least in this example it behaves or just looks to me like it isn't. How do you explain this?


Solution

  • How do you explain this?

    The same explanation that applies to just about every question when you attempt to explain observed behaviour where threading is involved:

    Threads are complicated beasts, generally adding code to observe what is happening will affect the threading, threading as a general concept is not repeatable (things depend on which song is playing in your music player to the phase of the moon), and it doesn't work the way you think it does, and the explanations and mental models provided are oversimplifications that do not hold up.

    It's not too hard to explain here, fortunately:

    1. Yes, it really is a separate thread. You can always run Thread.currentThread() to see this.

    2. Threads aren't some voodoo magic instantaneous thing, and your CPU isn't secretly a batch of entirely independent multitude of computers.

    The invokeLater call takes your code, adds it to a queue, and pings the EDT to wake up and process that queue, which now has 1 item in it. This queue gets lots of traffic; when you move another window over yours, the EDT gets pings to repaint. When you click a button, that gets added to the queue too, etc.

    Regardless of that traffic on that queue, the act of waking up a thread is not quite instant. Your main thread gets around to writing After much faster (not that a mere human can observe it, computers run a few million times faster than what we can observe with our eyeballs) than your computer system gets around to actually waking up that thread.

    Lob a bomb in there to see the effects. For example, add Thread.sleep(1000); right before sysout("After"); and observe what happens.

    Of course you observe what you did when you use invokeAndWait - that will do the exact same thing (add your code to the queue, and send a ping to the thread to go wake up), but it will then freeze your main thread. The code that got shipped off to the EDT will update some internal boolean ("Yup, I have run to the end", and send a ping back to the main thread to wake up, check that boolean, and keep going). Hence, it cant continue until your code (that prints Runnable, and true) has run.

    In other words:

    Your second snippet always outputs what you wrote.

    Your first snippet will output, well, who knows. The system is free to print B/R/t/A, or B/A/R/t, or even B/R/A/t in rare cases. As an additional problem, System.out is heavy and causes synchronization, so if you are printing whilst analysing threads, the stuff you observe is generally completely irrelevant.