Swing threading and the event-dispatch thread

The evolution and pitfalls of Swing's single-threaded event model

1 2 3 4 5 Page 3
Page 3 of 5

Swing threading

As Java GUI developers advanced from AWT to Swing, one of the key things pushed by Sun was the Swing toolkit's single-threaded programming model. Here's the single-thread rule from an early introduction to Swing threading:

Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.

-- Hans Muller and Kathy Walrath,

Threads and Swing

Swing's single-threaded approach removed the need to make GUI components thread safe by basically saying "do everything on the event-dispatch thread once the component is realized." In this context, a realized component is one whose paint() method could have been called, such as a component shown within a visible window. Once a component has been realized the system always executes event-handling code and draws screens and their components on the event-dispatch thread.

Updating the AWT button example to this early Swing initialization model results in the program shown in Listing 3. The new program adds one behavior: after pressing the button it waits three seconds before updating the button label. I've also taken advantage of the Swing JFrame's setDefaultCloseOperation() method, which wasn't available when using the AWT Frame class.

Listing 3. A typical Swing button

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SwingButton {
  public static void main(String args[]) {
    JFrame frame = new JFrame("Title");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    final JButton button = new JButton("Press Here");
    ActionListener action = new ActionListener() {
      Runnable changer = new Runnable() {
        public void run() {
          String label = button.getText();
          button.setText(label + "0");
        }
      };
      Runnable sleeper = new Runnable() {
        public void run() {
          try {
            Thread.sleep(3000);
            EventQueue.invokeLater(changer);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };
      public void actionPerformed(ActionEvent e) {
        System.out.println("Clicked");
        new Thread(sleeper).start();
      }
    };
    button.addActionListener(action);
    frame.add(button, BorderLayout.CENTER);
    frame.setSize(200, 200);
    frame.setVisible(true);
  }
}

Figure 2 shows the button that would result from the code in Listing 3.

A button component created using the Swing toolkit

Figure 2. A button component created using the Swing toolkit

Notes about the code

SwingButton adds some additional behavior to the program shown in Listing 2 -- it updates the button's text label by taking the old button label and appending a 0 to it. You can see the code to do this in the actionPerformed() behavior of the button click:

System.out.println("Clicked");
new Thread(sleeper).start();

This code says print "Clicked" to the console, start a new thread, and do the code from the sleeper runnable. Ignoring the exception handling, that code looks like so:

Thread.sleep(3000);
EventQueue.invokeLater(changer);

The first question you might ask is why not just sleep in the actionPerformed() method. The answer is simple: if the sleep() call happens there, the GUI will become non-responsive. In both Swing and AWT, the GUI handling code executes in the event-dispatch thread. This includes painting the UI.

You can test out this non-responsiveness by changing the above program's start() call to a run() call and then running the program. This essentially prevents a new thread from being created. You'll notice the button continues to look pressed-in for three seconds before it becomes unselected with the new button label. Another option would be to move the Thread.sleep() call into its own thread. You could then press the button multiple times in quick succession and it would look like the button had been pressed multiple times. After waiting three seconds after each press, the label would be updated.

Avoid a common error
Don't call the invokeAndWait() method of EventQueue from the event-dispatch thread. If you do, the event-dispatch thread will be blocked until the requested task runs on it. This latter task will never happen, so the event-dispatch thread will be forever blocked.

All of this brings me to the invokeLater() call. As I previously mentioned, updates to the Swing component state must be done on the event-dispatch thread. The EventQueue class has two methods to help with running code on the event thread: invokeLater() and invokeAndWait(). The invokeLater() method says to take the Runnable argument and have the thread scheduler run it at some later time on the event thread. The invokeAndWait() method says the same thing but with one added caveat: don't continue running the current thread until the invoked Runnable method finishes.

Looking back at the complete button-click behavior, it says to start a new thread and sleep for three seconds. On the event-dispatch thread it then gets the button label and appends a 0 to it. Forcing the get and set label methods to happen on the event-dispatch thread is an easy way to avoid thread synchronization, because the Swing component's state is only ever accessed on one thread.

1 2 3 4 5 Page 3
Page 3 of 5