Swing threading and the event-dispatch thread

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

1 2 3 4 5 Page 5
Page 5 of 5

Keeping Swing thread safe

The last step in creating a Swing GUI is to start it up. The correct way to start up a Swing GUI today differs from Sun's originally prescribed approach. Here's the quote from the Sun documentation again:

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.

Now throw those instructions out the window, because around when JSE 1.5 was released all the examples on Sun's site changed. Since that time it has been a little-known fact that you are supposed to always access Swing components on the event-dispatch thread to ensure their thread safety/single-threaded access. The reason behind the change is simple: while your program might access a Swing component off of the event-dispatch thread before the component is realized, the initialization of the Swing UI could trigger something to run on the event-dispatch thread afterward, because the component/UI expects to run everything on the event-dispatch thread. Having GUI components run on different threads breaks Swing's single-threaded programming model.

The program in Listing 5 isn't quite realistic, but it serves to make my point.

Listing 5. Accessing Swing component state from multiple threads

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

public class BadSwingButton {
  public static void main(String args[]) {
    JFrame frame = new JFrame("Title");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JButton button = new JButton("Press Here");
    ContainerListener container = new ContainerAdapter() {
      public void componentAdded(final ContainerEvent e) {
        SwingWorker worker = new SwingWorker() {
          protected String doInBackground() throws InterruptedException {
            Thread.sleep(250);
            return null;
          }
          protected void done() {
            System.out.println("On the event thread? : " +
              EventQueue.isDispatchThread());
            JButton button = (JButton)e.getChild();
            String label = button.getText();
            button.setText(label + "0");
          }
        };
        worker.execute();
      }
    };
    frame.getContentPane().addContainerListener(container);
    frame.add(button, BorderLayout.CENTER);
    frame.setSize(200, 200);
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
    }
    System.out.println("I'm about to be realized: " +
      EventQueue.isDispatchThread());
    frame.setVisible(true);
  }
}

Notice that the output shows some code running on the main thread prior to the UI being realized. This means that the initialization code is running on one thread while other UI code is running on the event-dispatch thread, which breaks Swing's single-threaded access model:

> java BadSwingButton
On the event thread? : true
I'm about to be realized: false

The program in Listing 5 will update the button's label from the container listener when the button is added to the container. To make the scenario more realistic, imagine a UI that "counts" labels in it and uses the count as the text in the border title. Naturally, it would need to update the border's title text in the event-dispatch thread. To keep things simple the program just updates the label of one button. While not realistic in function, this program shows the problem with every Swing program that has been written since the beginning of Swing's time. (Or at least all those that followed the recommended threading model found in the javadocs and online tutorials from Sun Microsystems, and even in my very own early editions of Swing programming books.)

Swing threading done right

The way to get Swing threading right is to forget Sun's original dictum. Don't worry about whether a component is realized or not. Don't bother trying to determine whether it is safe to access something off of the event-dispatch thread. It never is. Instead, create the whole UI on the event-dispatch thread. If you place the entire UI creation call inside an EventQueue.invokeLater() all accesses during initialization are guaranteed to be done in the event-dispatch thread. It is that simple.

Listing 6. Everything in its place

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

public class GoodSwingButton {
  public static void main(String args[]) {
    Runnable runner = new Runnable() {
      public void run() {
        JFrame frame = new JFrame("Title");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton button = new JButton("Press Here");
        ContainerListener container = new ContainerAdapter() {
          public void componentAdded(final ContainerEvent e) {
            SwingWorker worker = new SwingWorker() {
              protected String doInBackground() throws InterruptedException {
                return null;
              }
              protected void done() {
                System.out.println("On the event thread? : " +
                  EventQueue.isDispatchThread());
                JButton button = (JButton)e.getChild();
                String label = button.getText();
                button.setText(label + "0");
              }
            };
            worker.execute();
          }
        };
        frame.getContentPane().addContainerListener(container);
        frame.add(button, BorderLayout.CENTER);
        frame.setSize(200, 200);
        System.out.println("I'm about to be realized: " +
          EventQueue.isDispatchThread());
        frame.setVisible(true);
      }
    };
    EventQueue.invokeLater(runner);
 }
}

Run it now and the above program will show that both the initialization and container code are running on the event-dispatch thread:

> java GoodSwingButton
I'm about to be realized: true
On the event thread? : true

In conclusion

The extra work to create your UI in the event-dispatch thread might seem unnecessary at first. Everyone has been doing it the other way since the beginning of time, after all. Why bother to change now? The thing is, we've always been doing it wrong. To ensure your Swing components are accessed correctly you should always create the entire UI in the event-dispatch thread, as shown here:

Runnable runner = new Runnable() {
  public void run() {
    // ...create UI here...
  }
}
EventQueue.invokeLater(runner);

Moving your initialization code to the event-dispatch thread is the only way to ensure that your Swing GUIs are thread safe. Yes, it will feel awkward at first, but progress usually does.

John Zukowski has been playing with Java for well over 12 years now, having abandoned his C and X-Windows mindset long ago. With 10 books out on topics from Swing to collections to Java SE 6, John now does strategic technology consulting through his business, JZ Ventures, Inc..

Learn more about this topic

1 2 3 4 5 Page 5
Page 5 of 5