Asynchronous Operations in Swing

Originally published: 2010-01-18

Last updated: 2020-09-12

“Why does my program lock up?” That's a common question from new Swing programmers, and the usual answer is that they're doing some time-consuming operation in an event handler. They might be waiting for data from a socket connection, or loading a large file from disk. Because Swing uses a single thread to process events, this long-running operation freezes the UI: the user can't type or press a button, and none of the GUI components can repaint themselves.

This article looks at how to manage threads in Swing, keeping these long-running operations from affecting the UI. There's more to success than simply spinning up background threads. You need to get the results of these operations to display in the GUI, control the sequencing of not-quite-independent operations, and provide feedback to the user. We'll look at each of these goals, using an example program that manages files on Amazon's Simple Storage Service (S3).

It's All About Events

Graphical User Interfaces are truly a different paradigm from tradional programs, both from the user's perspective and the programmer's. Traditional programs are linear: you start, process some data, and then you're done. It doesn't matter whether your program is a transaction processing system written in COBOL in the 1960s, or a servlet written in Java last month; they both follow a linear execution path.

Graphical user interfaces, by comparison, receive a steady stream of independent but related events representing the user's actions: mouse moves, clicks, and keystrokes. Most of the time, these events are handled without any program code at all: if the user is typing in a text field, the events for her keystrokes are captured by the field itself; the program can later call getText() to read the field's contents. Other events are translated into high-level “actions”: when the user selects a menu item, Swing invokes a “listener” that responds to that action. These listeners seem to follow the linear paradigm: they start, do some work, then finish.

But there's an important caveat: in Swing, all action listeners are invoked on a single thread, which happens to be the same thread that Swing uses to dispatch low-level GUI events. If the listener takes a long time to run, those events won't be delivered to their components, and the GUI will “freeze up” until the action listener completes. And then all of the delayed events will be delivered, some of which may invoke other long-running listeners. Faced with a program that's unresponsive for several seconds, most users give up. Those that don't file bug reports.

As I said before, the events are independent but related: they represent an ongoing chain of user actions. Clearly, we need some rules for managing these interactions.

Rules for Interacting with the Event Thread

Like all rules, there are times when these should be broken. That said, here are the rules that I live by when writing Swing programs:

Rule #1: only access Swing components from the event thread.

This means every access. Yes, there are some GUI objects that are explicitly documented as being safe to call from another thread. And the real rule is that rendered components must be updated from the event thread. But it's easier just to follow the general rule and never touch any graphical object from a background thread.

Rule #2: do not run time-consuming operations on the event thread.

I'll keep repeating this: Swing uses a single thread for all GUI events. If your event handler is uploading a multi-megabyte file across the net, those events will be delayed until you're done. There is a balancing act involved with this rule: some operations (such as getting the size of a file) are so fast that they won't interrupt the user. However, that's not a reason to be cavalier, because some day your user will be getting the size of a file that resides on a fileserver that has an intermittent network connection.

Rule #3: do not access synchronized objects from the event thread.

This is really just a corollary to rule #2, and again, doesn't apply in all cases — after all, many Swing components use Vector, which is a synchronized object. The real concern is a synchronized method that will block for a long time when you call it, such as LinkedBlockingQueue.put().

Rule #4: no shared mutable state.

To me this is the most important rule of all, and is the main point of this article. The earlier rules are driven by the goal of keeping your users happy; this rule is driven by the goal of keeping you, the programmer, sane.

When you have shared mutable state, such as a TableModel that is updated from the event thread as well as a background thread, you make your program non-deterministic. If someone finds a bug, it may be caused by the timing of independent threads, and you may never be able to reproduce it. Mainstream software developers are slowly accepting the shared-nothing “actor” model of computation; good GUI developers have practiced it all along.

Inter-Thread Communication

So, in a shared-nothing world, how do you communicate between threads? The answer is simple: SwingUtilities.invokeLater(). When your long-running operation is done, it creates a Runnable that updates the GUI with its results. Here's how the interaction might unfold for a file download:

  1. The user selects a menu item to request the download.
  2. The action listener for this menu item constructs a Runnable containing all of the information needed to perform the download, such as the filename and destination directory. My preference is to define concrete classes for each operation, rather than attempting to use anonymous inner classes. Regardless, the instance is passed to a threadpool for execution.
  3. At some later time (perhaps immediately, perhaps after it's processed all outstanding work), the threadpool starts running the download operation. While running, this operation is not permitted to ask for additional information from the user, because that would block the background thread. Nor is it permitted to use data held by a GUI component, because they are only to be accessed from the event thread.
  4. When the download has completed, the operation puts together all the information that it wants to report back to the user (for example, the size of the file and the time it took to download), and packages it in another Runnable. That object is then passed to SwingUtilities.invokeLater(), which queues it for execution on the event thread.
  5. This second Runnable updates the GUI; perhaps it simply displays a dialog box with the download summary. In a well-designed two-stage operation, this second stage does't do a lot of work, so it won't affect the GUI.

The main thing to remember is that, once a Runnable is passed to another thread, the original thread never updates the data it holds. What happens on the other thread stays on the other thread.

AsynchronousOperation

So how should you execute tasks on their own thread? One approach is to simply call new Thread() every time you want to run something, passing it the Runnable that encapsulates your task. This has a couple of drawbacks. First, it has no inherent limits: you can keep creating new threads until you run out of virtual memory (although you'll probably can swamp the thread scheduler long before that happens).

A second, less well-known reason is that new threads inherit the priority of the threads that created them, and the event dispatch thread runs at a higher than normal priority (see this Sun technote). There's a good reason for running the event thread at a high priority: it improves UI responsiveness. But if background threads run at the same priority, that benefit is lost. In the worst case, with a lot of background threads running, the event thread may be starved for CPU leading to an unresponsive GUI.

As an alternative to creating your own threads, Sun provides the SwingWorker class. Prior to JDK 1.6 this class was an unsupported download; with the 1.6 release it became a full-fledged part of the javax.swing package. However, I've never particularly liked SwingWorker: early versions created new threads for each invocation, and the current version still manages its own threadpool. In my view, it's trying to do too much, and hides much of its operation from the programmer.

Instead, I use AsynchronousOperation, part of my SwingLib open source library. This class relies on the application managing its own threadpool (or spinning up ad hoc threads), and the implementation of its run() method is very simple:

public final void run()
{
    try
    {
        final T result = performOperation();
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                onComplete();
                onSuccess(result);
            }
        });
    }
    catch (final Throwable e)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                onComplete();
                onFailure(e);
            }
        });
    }
}

This class follows the Template Method pattern: it manages the sequence of operations, while you provide the behavior by overriding specific methods:

The design of the class — taking the return value from one method and passing it to another — encourages shared-nothing programming. You can, of course, define member variables that are accessed by all methods, but this is discouraged (you will, however, often create member variables to be used in one phase or the other, just not both).

Here's an example implementation that loads a list of files from Amazon S3:

@Override
protected FileListTableModel performOperation()
throws Exception
{
    _logger.debug("starting refresh");
    List<Map<String,String>> data = getBucket().listObjectsWithMetadata();
    SortedSet<S3File> result = new TreeSet<S3File>();
    for (Map<String,String> entry : data)
    {
        String key = entry.get(S3Constants.BUCKETLIST_KEY);
        String size = entry.get(S3Constants.BUCKETLIST_SIZE);
        String lastMod = entry.get(S3Constants.BUCKETLIST_LASTMOD);
        result.add(new S3File(key, size, lastMod));
    }
    _logger.debug("request complete: " + result.size() + " files");

    return new FileListTableModel(result);
}

@Override
protected void onSuccess(FileListTableModel result)
{
    getConcierge().getMainFrame().resetList(result);
}

As I noted above, the onSuccess() method is quite short: it takes the result and hands it off to the controller for the table. This reflects my design philosophy about partitioning knowledge within an application: the operation knows how to retrieve data, but it has no reason to know what happens to that data once it's retrieved.

While the implementation of performOperation() might not make a lot of sense unless you've used the S34J library, there is one thing that I want to point out: it does all of the work. The web service call happens at the very beginning; the rest of the method simply processes the returned data, and the result is a fully-initialized TableModel. That processing code could have been put in onComplete() without breaking any of the rules. But by running in on the background thread, I guarantee that the user won't notice any delays.

You may be wondering why this example omits the onComplete() and onFailure() methods. These will be shown later.

Providing Feedback

Moving long-running operations to a background thread is only part of creating a user-friendly application. Providing feedback to the user is often more important. The refresh operation above generally takes a few hundred milliseconds to execute. That's fast, but still something that a user would notice: “I've clicked the menu, nothing's happening!” Uploading or downloading files, however, is guaranteed to be noticed.

The Busy Cursor

One of the simplest ways to indicate that the application is busy is to change its cursor to a “busy cursor”: window showing busy cursor

The busy cursor is one of the “predefined” cursors provided by the Cursor class. And all Swing components have a setCursor() method that changes the cursor displayed when the mouse is over that specific component (see the CursorDemo example).

Changing the cursor over a single component can be useful: a complex application might update one set of components while the user is still able to interact with others; you could show the busy cursor over the first set and the normal cursor over the second.

Most of the time, however, you'll set the wait cursor on the application's root pane to indicate overall status. But this introduces its own complexity: what if there are multiple operations happening at once? If you simply switch to the busy cursor at the start of an operation, and back to the normal cursor when it's done, overlapping operations will leave you with the wrong cursor. Instead, you have to create a stack of cursors (these methods methods are copied from CursorManager, another part of the SwingLib library):

public void pushCursor(JComponent comp, Cursor newCursor)
{
    LinkedList<Cursor> stack = getCursorStackFor(comp);
    stack.add(comp.getCursor());
    comp.setCursor(newCursor);
}

public void popCursor(JComponent comp)
{
    LinkedList<Cursor> stack = getCursorStackFor(comp);
    if (stack.size() == 0)
        return;
    comp.setCursor(stack.removeLast());
}

Incorporating a Busy Cursor into AsynchronousOperation

AsynchronousOperation is a two-phase operation: it starts running on a background thread, then completes on the event thread. But to incorporate a busy cursor you need a three-phase operation, which starts running on the event thread and then puts itself on the background thread. Since I don't want AsynchronousOperation to manage its own threads, this calls for an application-specific subclass:

public abstract class AbstractS3Op<T>
extends AsynchronousOperation<T>
{
    private Concierge _concierge;
    private String _description;

    // member variables specific to the application

    protected AbstractS3Op(Concierge concierge, String description)
    {
        _concierge = concierge;
        _description = description;
    }


    public void start()
    {
        _concierge.getMainFrame().setBusyState(true);
        _concierge.execute(this);
    }


    @Override
    protected void onComplete()
    {
        _concierge.getMainFrame().setBusyState(false);
    }

The Concierge will be described in another article; it's my way to avoid singletons yet still provide easy access to shared resources. One of these resources is the background thread pool, and the last step of the start() method is to pass the operation object to that pool.

The busy-cursor feedback is managed by the main window controller; this application just has a single window, so it makes sense to put the logic there. In an application with multiple independent windows, I would have a central CursorManager, accessed via the Concierge.

We want to change the cursor back to its normal state regardless of whether the operation succeeded or failed. This is the use case for onComplete(). As you saw above, it is executed on the event prior to both onSuccess() and onFailure(). But be careful: because it's executed before these methods, it must not throw or they'll never be executed.

ProgressMonitor

While a busy cursor is useful, it doesn't provide a lot of feedback to the user. For a long-running operation, such as a file upload, it's better to display a progress dialog. If you update that dialog as the operation is running, better still. The JDK provides ProgressMonitor to fill this role, but it has a few nasty quirks:

As a result, I've implemented my own version (part of SwingLib, of course). And using it, the start() and onComplete() methods of AbstractS3Op actually look like this:

public void start()
{
    setBusyState(true);
    _concierge.execute(this);
}

protected void onComplete()
{
    setBusyState(false);
}

private void setBusyState(boolean isBusy)
{
    _concierge.getMainFrame().setBusyState(isBusy);
    if (isBusy)
    {
        _progressMonitor = new ProgressMonitor(
                                _concierge.getDialogOwner(),
                                "S3 Operation in Progress",
                                _description,
                                ProgressMonitor.Options.MODAL,
                                ProgressMonitor.Options.CENTER,
                                ProgressMonitor.Options.SHOW_STATUS);
        _progressMonitor.show();
    }
    else if (_progressMonitor != null)
    {
        _progressMonitor.dispose();
        _progressMonitor = null;
    }
}

protected void updateProgressMonitor(String message)
{
    _progressMonitor.setStatus(message);
}

Unlike the JDK's progress dialog, which decides when it should appear based on elapsed time, my operations display the progress dialog immediately. The updateProgressMonitor() method is meant to be called while the operation is running, to give the user constant feedback. Here's an example from the delete operation, which displays each file as it's deleted:

@Override
protected Object performOperation()
throws Exception
{
    _logger.debug("starting request");
    for (S3File file : _files)
    {
        _logger.debug(file);
        String key = file.getKey();
        S3Object obj = createS3Object(key);
        obj.delete();
        updateProgressMonitor(file.toString());
    }
    _logger.debug("request complete");
    return null;
}

And, just in case you're wondering, the progress monitor internally uses SwingUtilities.invokeLater() to update the actual dialog:

public void setStatus(final String message)
{
    SwingUtilities.invokeLater(new Runnable()
    {
        public void run()
        {
            _fStatus.setText(message);
        }
    });
}

Interactive Updates

Another way to provide feedback from long-running operations is to update the UI while the operation is running. For example, as you upload files via S3Util, not only does the progress dialog tell you what's happening, but you can see rows being added to the main list: window showing progress dialog

Within the loop that uploads files, there's a call to this method:

private void reportFileUploaded(final S3File file)
{
    SwingUtilities.invokeLater(new Runnable()
    {
        public void run()
        {
            getConcierge().getMainFrame().addFile(file);
        }
    });
}

With the uploads being reported as they happen, there's no reason for the operation to call onSuccess(). But that's OK, the default implementation in AsynchronousOperation is empty.

From a user experience perspective, such in-your-face updates can be annoying: I'm sure we've all visited websites that display a popup while in the middle of a long operation. And if you have a lot of updates, you'll reduce the overall throughput of your program (at least on a single-core processor), since the UI will be constantly repainting. So put yourself in the position of the user before adding interactive updates to your code, and avoid using them when the user doesn't receive some benefit (in the case of file uploads, it can be useful: if you see that files are going into the wrong directory you could hit the “Cancel” button — except that S3Util 1.0 doesn't have one).

Exception Handling

The default implementation of AsynchronousOperation.onFailure() wraps the passed exception in RuntimeException and rethrows it; the Swing event thread logs all uncaught exceptions but otherwise ignores them. This is almost guaranteed to be the wrong thing to do, but the right thing depends on the application. In the case of S3Util, a tool meant for programmers and system administrators, AbstractS3Op provides a simple implementation:

@Override
protected void onFailure(Throwable ex)
{
    LogFactory.getLog(this.getClass()).error("request failed", ex);
    JOptionPane.showMessageDialog(
            _concierge.getDialogOwner(),
            "Unable to process this request: " + ex.getMessage()
                + "<br>See log for more information",
            "Unable to Execute Operation",
            JOptionPane.ERROR_MESSAGE);
}

In an application targeted to end users, you'll want to do more than this. For example, in S3Util a request might fail because the user entered an invalid access key. This information is available in the exception and I could use this fact to display an appropriate message, along with a button that opens the Preferences dialog so that the user could remedy the situation.

Better still is to prevent onFailure() from being invoked in the first place. As with a normal Java application, if you can correct an exception when it is thrown, you should do so. The reason is that you lose context as you move away from the code that caused the exception; in a Swing app, you're no longer even on the same thread. So if you can correct the situation without user input, do so and try the operation again. Just be careful that you don't create a retry loop that will never end.

How Many Things Do You Want to Run Simultaneously?

When you execute long-running operations on a threadpool, you'll ask yourself “how many threads do I need?” As multi-core processors become more common, it's tempting to say that the pool should have at least as many threads as you have processors. Or more, if your operations tend to be IO-bound.

That answer, however, ignores the fact that humans aren't very good at multi-tasking, and running too many simultaneous operations can confuse your users. S3Util, for example, has operations (upload, delete, download) that probably shouldn't happen at the same time — why would you ever delete files in the middle of an upload? As a result, it has a single thread in its threadpool, and all operations display modal dialogs.

That said, a lot of applications have CPU-intensive “set and forget” operations such as analyzing data, several of which can be running while the user is interacting with another part of the program. For such an application, I'd think of using two threadpools, one with a single thread for interactive operations, one with many threads for true background operations.

Not Everything Needs Its Own Thread

AsynchronousOperation does impose costs of its own: there's a delay before it starts running, a delay as its results are posted back to the event thread, and then another delay as the component repaints. It's possible that these delays will take longer than just executing the operation inline. For example, TableRowSorter, a Swing utility class for enabling sortable columns in a JTable, does all of its work on the event thread. For most usable tables (those with at most a few thousand rows), this happens so fast that the user will never notice. For larger tables, there will be a delay, but if the user is working with such large tables they'll expect that delay.

A useful rule-of-thumb is that any operation that relies on resources external to the JVM — particularly resources accessed over a network — should be performed on its own thread, while it's OK to process in-memory data on the event thread. There is a gray area in the middle: numerical analysis, for example, may use in-memory data yet still take significant time to excute.

Ultimately, the choice of whether to use background threads requires thought and experimentation. And always consider whether your development machine is faster or slower than the average user's machine. What you think is acceptable may be horrendously slow to someone without a leading-edge machine (I know of one company that intentionally gave its developers underpowered machines — but then switched from desktop applications to web-apps, where the end-user's machine didn't matter).

What About SwingUtilities.invokeAndWait()?

Throughout this article I've used SwingUtilities.invokeLater() to move data from a background thread to the event thread. But there's also an invokeAndWait() method. Should you ever use it, and if not, why not?

My answer is “almost never,” and the rationale is that it breaks rule #3. The call to invokeAndWait() will block until its Runnable finishes, which means that the background thread won't be able to do anything else. If you're concerned that you need to order the operations on the event thread, don't be: Runnables passed to invokeLater() are invoked in the order they were submitted.

There are some cases where you'll want to chain operations together: run one operation on the background thread, update the GUI, and only then start the next operation. Most of the time, this need is also an illusion: sequenced operations usually require user interaction. In the cases where you truly need to sequence operations, it's far better to implement that sequencing in the onSuccess() method.

In my programs, there's only one place where I call SwingUtilities.invokeAndWait(): application startup. And even here, I think my use is driven more by dogma than reality: I don't want to risk the main application thread ending (and the program exiting) before the event thread takes over. So my Swing programs' main() methods all look like this:

public static void main(String[] argv)
throws Exception
{
    final Concierge concierge = new Concierge(new ConfigBean());
    SwingUtilities.invokeAndWait(new Runnable()
    {
        public void run()
        {
            new MainFrameController(concierge).buildAndShow();

            // any additional GUI startup tasks
        }
    });
}

A Tip to Improve Responsiveness: Do Work Only When It's Necessary

If you browse the S3Util source code (linked below), you'll see that I create dialog controller objects when the program starts, but don't construct the actual dialogs until the first time they're displayed. This “lazy instantiation” significantly improves application startup time, and does not impact interactiveness — creating a single dialog in an ad hoc manner doesn't take a lot of time.

It's the little things like this that lead to an application's “feel.” Users notice short delays subliminally. They'll never be able to tell you what needs to be fixed, just that it “runs slowly.” But if you pay attention to how much work happens on the event thread, and try to minimize it, performance complaints should be rare. And that's the real goal.

For More Information

The S3Util source code is available on GitHub.

It uses the swinglib library, which contains AsynchronousOperation along with other helper classes that I developed when I was actively working with Swing. It's available as a JAR from Maven Central in case you'd like to use it as well, but beware that it is not actively maintained.

If you want to see just how annoying an unresponsive UI can be, run this) example.

Sun has released many articles on Swing and threading; here are some that I've found useful:

Copyright © Keith D Gregory, all rights reserved

This site does not intentionally use tracking cookies. Any cookies have been added by my hosting provider (InMotion Hosting), and I have no ability to remove them. I do, however, have access to the site's access logs with source IP addresses.