Event Dispatch Thread (EDT)

In another section we created and ran a simple Swing application that displayed a frame, and if you compile and run that application you'll find that the Java Virtual Machine (JVM) process remains active even if you click on the frame's close button. In fact, with that application the only way to terminate the JVM process is to do so manually using Control-C if it was started from the command line, or by taking an equivalent action through your integrated development environment (IDE), such as clicking on the Terminate button in the Eclipse console view.

If you've written Java applications before it may be surprising to you that you have to explicitly terminate the application, because the JVM normally terminates on its own after executing a Java application. For example, let's try creating and running a different application that simply sends "Hello, world!" to standard output:

public class HelloWorldConsole { public static void main(String[] args) { System.out.println("Hello, world!"); } }
Listing 1: A Java application that sends "Hello, world!" to standard output.

When you compile and run this program, it displays "Hello, world!" and the JVM process exits immediately afterwards. That seems appropriate because, after all, the program has done everything it was supposed to do and there's no reason that the JVM process should remain active. But why did the JVM not terminate after running our HelloWorld Swing application? Before we look at why the Swing application doesn't exit automatically, let's first see what would happen if it did terminate immediately, which we can do by making the temporary change to HelloWorld shown in bold below:

import javax.swing.JFrame; public class HelloWorld { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); System.exit(0); } }
Listing 2: The HelloWorld application with a call to System.exit() added.

System.exit() is really just a convenience method that invokes Runtime.exit(). System.exit() is more convenient to use because it's static and is described in the API documentation as the "conventional" (preferred) way of terminating the JVM.
Running the application now shows what happens when the JVM does terminate immediately after displaying the frame: the window appears on the screen very briefly and immediately disappears before you can interact with it. That's because when a JVM exits it eliminates any windows it had created, which means that the behavior of the original Swing application was appropriate: the JVM was somehow prevented from exiting, which allowed the frame we created to remain visible.

Even knowing that the Swing application's behavior is appropriate, we're still left with the question of how the program prevented the JVM from exiting. As it turns out, the JVM is kept alive due to our Swing code having indirectly triggered the creation of a new thread called the Event Dispatch Thread (EDT). The EDT plays a very critical role in Swing applications and some level of understanding of the EDT is one of the most important parts of knowing how to develop well-behaved Swing applications.

So how exactly does the EDT keep the JVM from exiting? To understand the answer to that question, let's first look at why the JVM does exit in the case of our console application defined in Listing 1, which can be understood if you review the Application Programming Interface (API) documentation for Java's Thread class, which reads in part:

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

  • The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
  • All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.
Daemon and non-daemon threads are functionality identical aside from the one side-effect described here. In other words, the choice of daemon versus non-daemon for a thread is based solely on whether that thread being alive should prevent the JVM from terminating.
In other words, the JVM process terminates once it detects that only daemon threads exist or when it's explicitly asked to terminate via the Runtime.exit() method or the System.exit() equivalent. With this information in mind, it's helpful to have a visual representation of what occurs when you run the HelloWorldConsole application along with a description of the sequence that occurs:

  1. A user starts the JVM and specifies the name of an entry point class containing a static main() method.
  2. The JVM creates a non-daemon thread commonly referred to as the main thread.
  3. The main thread enters the static main() method of the user-specified entry point class.
  4. The main thread exits the static main() method.
  5. The main thread terminates.
  6. The JVM detects that no more non-daemon threads are active.
  7. The JVM process ends.
Process and thread creation and termination diagram
Figure 1: Sequence of process and thread lifetimes when running HelloWorldConsole application.
Again, the behavior seen with both the HelloWorld and HelloWorldConsole is normal, and the difference in behavior is due to the EDT being activated during execution of the Swing application. That sequence of events is described and illustrated below:

  1. A user starts the JVM and specifies the name of an entry point class containing a static main() method.
  2. The JVM creates a non-daemon thread commonly referred to as the main thread.
  3. The main thread enters the static main() method of the user-specified entry point class.
  4. The main thread executes user interface code that triggers the creation of the EDT.
  5. The main thread exits the static main() method.
  6. The main thread terminates -- but the JVM process remains alive.
Application process flow diagram
Figure 2: Sequence of process and thread lifetimes when running HelloWorld application.
So now we can explain the behavior that was observed: the JVM didn't terminate because the EDT was started and remained alive, which in turn kept the JVM from exiting. So specifically which part of our HelloWorld triggered the creation of the EDT and why? The EDT creation is triggered by the line that makes the frame visible:
frame.setVisible(true);
If you temporarily delete or comment out that line and run the program again you'll see that -- as you'd expect -- not only does the frame never appear, but the JVM terminates automatically just as it did when we executed the HelloWorldConsole application in Listing 1.

Keeping the JVM from exiting is a useful characteristic of the EDT, but that's not its only purpose or even its primary one. Among other things, the EDT is responsible for the following activities that typically need to be handled while a window is displayed on the screen:

  • Painting or rendering Swing components; that is, literally drawing a represention of each component that appears on the screen.
  • Handling mouse and keyboard input that affects the state, appearance, and behavior of the user interface.
For example, when a Swing application displays a button, that button is rendered by Swing code. Java includes libraries that provide the ability to draw dots, lines, squares, and other shapes on the screen as well as to select which color is used while drawing, and Swing makes extensive use of that capability to render its components.

As you might expect, the amount of code involved in drawing even a simple component like a button -- at least one with an aesthetically pleasing appearance -- is significant. However, that work is abstracted into a library that's provided for you, and you won't normally need to be concerned with how to actually draw a button or any other component.

Now that you have a better understanding of the EDT's responsibilities, it's probably more obvious why the EDT is automatically started when the frame was made visible: because it was needed to draw the frame's content and to respond to any relevant user input. What's probably not as obvious, though, is that our simple "Hello, world!" program has a flaw in it. Specifically, it creates and modifies a Swing componently -- namely a JFrame instance -- in a thread other than the EDT.

Thread Safety In Swing

JFrame and the other classes in Swing are supported by a large amount of code, almost none of which is designed to be thread safe. Instead, the vast majority of the Swing code is designed with the assumption that it will be executed by exactly one thread, and that thread is the EDT. What this means in practice is that if you write an application that allows any other thread to access or modify Swing components directly, that application may or may not work correctly. In fact, it may work correctly at some times and fail at others.

This type of unpredictable behavior isn't specific to Swing but is the nature of application that aren't correctly designed to accomodate multiple threads. A detailed discussion of how to correctly use threads is beyond the scope of this tutorial, but suffice it to say that you should strive to avoid threading problems, because they tend to be difficult to diagnose and can be even harder to correct.

So how do we reconcile the need to update our user interface with the requirement that only the EDT be allowed to perform the updates? As we'll see later, this isn't usually as big a burden as it may initially appear, because in most cases the code that updates the user interface will be called by the EDT while it's processing user input. In those cases it's entirely appropriate to have your code update the Swing widgets, but unfortunately that's not the case with our HelloWorld application. In this sample program it's the main thread -- not the EDT -- that constructs and initializes our frame, so how can we change the implementation to make it thread-safe?

You may be wondering why the creators of Swing didn't save programmers a lot of trouble by making Swing thread-safe. Unfortunately, as explained in a blog post authored by Graham Hamilton titled, "Multithreaded toolkits; A failed dream?", it isn't that simple.
Again, the issues related to thread safety aren't unique to Swing applications, and one of the techniques used to write thread-safe code in Java is to place code in the run() method of an implementation of the Runnable interface and pass an instance of that implementation class to a thread for execution. One of Swing's support classes is SwingUtilities, and invokeLater() is a static method in that class that which allows you to pass a Runnable object as a parameter. That method generates a request that will eventually be processed by the EDT, and when it encounters the request it will invoke the run() method of the Runnable object you passed to invokeLater(). In other words, invokeLater() is a method that allows you to identify code you want to have executed by the EDT.

Note that invokeLater() is asynchronous, meaning that it returns immediately without waiting for the execution of the Runnable you specified to be completed. The actual point in time when that code gets executed is technically unspecified and theoretically can occur at any time after it's queued (hence the "later" in its name), though in practice it usually will be processed by the EDT almost instantly.

In contrast to the asynchronous nature of invokeLater(), the SwingUtilities class also includes the similarly named invokeLater() method, which also queues a Runnable object, but is synchronous -- that is, it doesn't return until after the EDT has finished executing the code in your Runnable object's run() method.

The EDT functions by maintaining a queue (list) of tasks that it needs to perform, such as the processing of a mouse or keyboard event or the painting of part or all of a window. When you call one of these SwingUtilities methods, the Runnable you specify is referenced by a task added to the EDT's queue, and when it encounters that task it will call the run() method of your Runnable object.

Listing 3 illustrates one way of passing code to the EDT for execution. It includes a class called FrameCreator that contains the same code we used before, but which is now encapsulated inside the run() method of a Runnable implementation:

import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ThreadSafeHelloWorld { public static void main(String[] args) { Runnable creator = new FrameCreator(); SwingUtilities.invokeLater(creator); } static class FrameCreator implements Runnable { public void run() { JFrame frame = new JFrame(); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); } } }
Listing 3: A simple "Hello, world!" Swing application.

Inner Classes In Swing

The code in Listing 3 uses an inner class (FrameCreator) because the code in that class is very closely tied to the enclosing (ThreadSafeHelloWorld) class and because there's no benefit to defining it in a separate source code file. The use of inner classes is very common in Swing application code, partly due to the need to encapsulate code in Runnable implementations as we did here. An even more concise way of doing this is to use an anonymous inner class, because anonymous inner classes eliminate some of the boilerplate code: that is, some of the code that's essentially the same each time it's implemented. In this case the boilerplate code is the definition of the FrameCreator class, which could instead be implemented using an anonymous inner class as shown in Listing 4:

import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ThreadSafeHelloWorld { public static void main(String[] args) { Runnable creator = new Runnable() { public void run() { JFrame frame = new JFrame(); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); } }; SwingUtilities.invokeLater(creator); } }
Listing 4: A thread-safe implementation using an anonymous inner class.

As the term implies, an anonymous inner class isn't explicitly given a name. An anonymous inner class does in fact have a name, but it's assigned automatically by the Java compiler and you won't typically need to know or care what name was assigned.

This code is functionally identical to the previous implementation, with the only difference being that we use an anonymous inner class instead of a named inner class. As of Java 8, however, there's an even shorter syntax you can use by taking advantage of a new language feature called lambda expressions, which allow you to even omit the method declaration when the relevant interface is a functional interface. An example of how a lambda expression can be used is shown in Listing 5, and this abbreviated format will be used in the remainder of this tutorial:
import javax.swing.JFrame; import javax.swing.SwingUtilities; public class ThreadSafeHelloWorldLambda { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame(); frame.setTitle("Hello, world!"); frame.setSize(400, 225); frame.setVisible(true); }); } }
Listing 5: A thread-safe implementation using an anonymous inner class.

Long-Running Tasks

As mentioned earlier, the code you pass to the EDT using invokeLater() or invokeAndWait()is treated as a task for it to complete and is placed on the queue along with other tasks such as painting / rendering and the handling of user input.

To ensure that your Swing application remains responsive, a key point to remember is that you should not place any code on the EDT's queue that might represent a long-running task. While the EDT is running your code it will be unable to repaint the window or respond to user input, and user code executed by the EDT that takes too long to complete can manifest itself in a Swing application as a user interface that suddenly seems to "hang" or "freeze": that is, the UI doesn't get repainted and / or doesn't respond to user input via keyboard or mouse.

What constitutes "long-running" is somewhat subjective and is left to your discretion, but a good rule of thumb is that any operation that might take longer than one second should probably not be executed by the EDT, and even that is too long if you want your user interface to be very responsive.

Perhaps the scenarios where this most commonly becomes a problem is when you write code that reads from or writes to a database or that performs some function that involves waiting on network communications to complete. That code may typically complete very quickly, but if there's a delay due to database locking or network congestion, it can impact your user interface in a very negative way. On the other hand, if the code you want to have executed by the EDT only involves in-memory processing, you typically won't need to be concerned with the amount of time it takes to complete.

Sometimes, though, such potentially long-running operations are necessary and unavoidable, and as we'll see later, Swing does provide a mechanism to accomodate those, although it's slightly more complicated than the simple invocations of invokeLater() and invokeAndWait() discussed here.