Threading with Swing (ctd):
SwingUtilities.invokeLater()

The SwingUtilities.invokeLater() method is an extremely important method to know about if you are writing a Java application that uses multithreading and your program uses Swing for its user interface. In our introduction to threading with Swing, we said that any updates to the user interface must happen on the event dispatch thread. So from any other thread— in practice, that means code that isn't called directly from an event handler— we must specifically arrange for our GUI update code, and generally only that code, to be called on the event dispatch thread.

So, supposing we have a button that launches a series of database queries. We dutifully start up a new thread so that our queries won't block the user interface:

JButton b = new JButton("Run query");
b.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    Thread queryThread = new Thread() {
      public void run() {
        runQueries();
      }
    };
    queryThread.start();
  }
});

That was the easy bit. But now, from our query thread, we want to update a progress bar or some other component showing the current progress to the user. How can we do this if we're no longer in the event dispatch thread? Well, the SwingUtilities class, which provides various useful little calls, includes a method called invokeLater(). This method allows us to post a "job" to Swing, which it will then run on the event dispatch thread at its next convenience. So here is how to use SwingUtilities.invokeLater() from out runQueries method:

// Called from non-UI thread
private void runQueries() {
  for (int i = 0; i < noQueries; i++) {
    runDatabaseQuery(i);
    updateProgress(i);
  }
}

private void updateProgress(final int queryNo) {
  SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      // Here, we can safely update the GUI
      // because we'll be called from the
      // event dispatch thread
      statusLabel.setText("Query: " + queryNo);
    }
  });
}

Here, statusLabel would be a JLabel or JTextField or something of that ilk— it doesn't matter terribly much. The point is: whatever GUI component it is, we must make sure that the code to update it is inside a call to invokeLater().

There's a bit of awkward syntax that we've glossed over, but which it's important to get used to for Swing programming generally. Essentially, we use an anonumous inner class to define our "job"— more specifically, an implementation of the Runnable interface. Anonymous inner classes are a bit of syntactic shortcut. We could also have written something like:

class UpdateJob implements Runnable {
  private final String progress;
  UpdateJob(String progress) {
    this.progress = progress;
  }
  public void run() {
    statusLabel.setText(progress);
  }
}
...
Runnable task = new UpdateJob("Query: " + i);
SwingUtilities.invokeLater(task);

But usually, it's a bit tedious to have to write a separate class definition for every pattern of update job. (Note that either way, they still compile to a different class.)

Application startup code

There's one place where it's very easy to forget that we need SwingUtilities.invokeLater(), and that's on application startup. Our applications main() method will always be called by a special "main" thread that the VM starts up for us. And this main thread is not the event dispatch thread! So:

The code that initialises our GUI must also take place in an invokeLater().

So our initial main() method should look something like this:

public class MyApplication extends JFrame {

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyApplication app = new MyApplication();
        app.setVisible(true);
      }
    });
  }

  private MyApplication() {
    // create UI here: add buttons, actions etc
  }
}

If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.