[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Tech Topics

The Last Word in Swing Threads
Working with Asynchronous Models

By Joseph Bowbeer

This is not an article in any particular series, but it is the third article that The Swing Connection has published on the use of threads in Swing.

The first article, Threads and Swing, explained Swing's single-thread rule. It now resides in The Swing Connection Archive.

The second article, Using a Swing Worker Thread, demonstrated the use of a SwingWorker thread utility class. It has also now been archived.

This article introduces a revised SwingWorker class, and demonstrates two methods for using threads with model-based components like JTable and JTree.

To understand the material presented in this article, it helps to have some familiarity with SwingWorker and the JTable and JTree components. You can find articles on the JTable and JTree components in the Tech Topics section, and also in The Swing Connection Archive. For more information about resource materials, see the Resources section at the end of this article.

This article is divided into the following major sections:

Introduction and review
Dynamic tree
Remote table
SwingWorker revised
Downloading
Conclusion
Resources
About the author


Introduction and Review

Before delving into trees and tables and asynchronous models, I want to first review Swing's single-thread rule and examine its implications.

Swing's single-thread rule says that Swing components can be accessed by only one thread at a time. This rule applies to both gets and sets, and the single thread is generally the event dispatch thread.

The single-thread rule is a good match for UI components because they tend to be used in a single-threaded way anyway, with most actions being initiated by the user. Building thread safe components is difficult and tedious: it's a good thing not to be doing if it can be avoided. But for all its benefits, the single- thread rule has far-reaching implications.

Swing components will generally not comply with the single-thread rule unless all their events are sent and received on the event-dispatch thread. For example, property-change events should be sent on the event-dispatch thread, and model-change events should be received on the event-dispatch thread.

For model-based components such as JTable and JTree, the single-thread rule implies that the model itself can only be accessed by the event-dispatch thread. For this reason, the model's methods must execute quickly and should never block, or the entire user interface will be unresponsive.

But suppose you have a JTree with a TreeModel that accesses a remote server? You may find that when the server is unavailable or is bogged down, the calls to your TreeModel block, freezing the entire user interface. What can you do to make the interface more responsive?

Or suppose you have a JTable with a TableModel that manages a device on the network? You may find that your interface crawls when the managed device is busy or when the network is congested. What can you do?

These situations call for threads. And when the need arises, there are several ways to use threads with Swing, despite the single-thread rule.

This article presents two methods for using threads to access slow, remote, or otherwise asynchronous models, and demonstrates their use with the JTree and JTable components. (A model that is not accessed solely by the event- dispatch thread is said to be asynchronous with respect to that thread.)


Dynamic Tree

Suppose you have a JTree with a TreeModel that accesses a remote server, but the server is slow or unreliable?

DynamicTree demonstrates the use of a background thread to dynamically expand the nodes of a JTree.

Figure 1 shows DynamicTree in the process of expanding a node.

Dynamic Tree
Figure 1.

Split-model design

DynamicTree is based on a split-model design. In this design, the real model is the asynchronous model, which may be slow or unreliable. The JTree component uses a standard, synchronous model to maintain a snapshot of the real model. (The asynchronous model may reside on a remote server and for this reason I often refer to it as the remote model, and I refer to the Swing component's model as the local model.)

Using a local model to shadow or cache the remote model has the advantage of providing a consistent, responsive model to the Swing component at all times. A disadvantage, one of the tradeoffs, is the duplication of model data. Another problem is that the two models are not always in synch, and they must be coordinated in some way.

Update as expanded: dynamic browsing

One way to coordinate the models is to update the local model only as the data is needed, and not before. This technique is useful when the remote model is slow or very large, but is basically static. DynamicTree uses this method to browse a slow, static tree model.

DynamicTree starts out as an unexpanded root node, while an expansion listener waits for the user to expand a node. When a node is expanded, the expansion listener starts a SwingWorker thread. The worker's construct() method accesses the remote model and returns new children for the expanded node. Then the worker's finished() method, running on the event-dispatch thread, adds the children to the local model.

For simplicity, only one worker is permitted to run at a time. When any node is collapsed, the active worker, if any, is interrupted. (No effort is made to see if the collapsed node is an ancestor of the node the worker is expanding.)

Execution sequence

Figure 2 shows the node expansion process. Execution begins and ends in the event-dispatch thread, on the left. SwingWorker execution begins on the right, in the worker's thread. The solid arrows are method calls, the dashed arrows are returns, and the half arrows indicate asynchronous invocation.

DynamicTree sequence diagram
Figure 2. DynamicTree execution sequence.

The rest of this section describes the class structure and implementation. Read on or skip ahead to the remote table demo. The Downloading section explains how to download and run this demo.

Implementation

Figure 3 shows a rough diagram of the class structure.

DynamicTree class diagram
Figure 3. DynamicTree classes.

The local model is a DefaultTreeModel containing DefaultMutableTreeNode nodes. The nodes must be mutable in order for their children to be added dynamically. The mutable node's userObject is a handy place to store a link back to the remote model.

TreeNodeFactory

The remote model implements the TreeNodeFactory interface:

public interface TreeNodeFactory {
  DefaultMutableTreeNode[] createChildren(Object userObject)
      throws Exception;
}

The createChildren() method is called in the worker thread. Like most asynchronous or remote methods, it may throw an exception (typically a RemoteException or an InterruptedException). The userObject parameter is the newly-expanded node's link back to the remote model. Returning all the children in an array is more efficient than returning each child individually, and avoids dealing with partial failure.

Initially, each child node is assigned an allowsChildren property and a link back to the remote model. (The allowsChildren property is set false if the remote node is a leaf; otherwise it is set true to indicate that the node can be expanded.)

DefaultTreeNodeFactory is an Adapter (see Design Patterns by The Gang of Four). It adapts any TreeModel to the TreeNodeFactory interface. The default tree model is used in the demo. Some commented-out code in the main method installs a FileSystemNodeFactory. SlowTreeNodeFactory is a wrapper used for simulation purposes; it inserts random delays.

Future work

I've tried to keep DynamicTree simple. The nodes have no content, besides their name. In a situation where the nodes have substantial content, it might be helpful to load the content asynchronously, perhaps using a tree selection listener to initiate the loading.

The remote table demo in the next section is more sophisticated.


Remote Table

Suppose you have a JTable with a TableModel that manages a remote device, but the interface really crawls when the device is busy?

The remote table demo uses background threads and asynchronous callbacks to view and modify a remote table model.

Figure 4 shows a remote table editor sending a new value to the server. The pending cell will remain yellow until the update is complete.

Remote Table Editor
Figure 4.

RemoteTable bean

The remote table demo is implemented with RMI and consists of a server and two clients: an editor and a viewer. The viewer is actually just an editor with its editing capability disabled.

The clients use a RemoteTable component bean, which is a subclass of JTable designed to work with remote table models. The editor shown in Figure 4 consists of a RemoteTable component, a status label, a simple activity meter (bottom right), and some code to locate the server.

Update when notified

Unlike DynamicTree, which updates its local model only when data is needed, the RemoteTable component begins by fetching all the data, and then listens for subsequent changes. This notification-based technique can be used with dynamic models, and works best when the models are fairly small.

Cell editing drives the notification. When the user finishes editing a cell, RemoteTable marks the edited cell with a pending value (yellow highlight) and schedules a SwingWorker task. The worker's construct() method sends the new value to the remote model.

When the remote model receives the new value, it notifies its listeners of the change. The worker's finished() method does nothing more than indicate the task has completed; the yellow pending cell turns back into a normal cell when notification from the remote model is received and processed.

Task queue

RemoteTable schedules its SwingWorker tasks on a QueuedExecutor, which runs the tasks sequentially on a single thread. (QueuedExecutor is part of Doug Lea's util.concurrent package - see Resources.) The remote model notifies its listeners using RMI callback.

To support visual feedback, RemoteTable sends Task events to registered Task listeners. The listener's taskStarted() method is called when a task is scheduled, and taskEnded() is called when it completes. The demo clients use these events to start and stop a little animation and update the status line.

Execution sequence

Figure 5 shows the cell update process. Execution begins and ends in the event-dispatch thread on the left. SwingWorker tasks run in the executor's thread on the right. Execution of the worker's finished() method is not shown.

RemoteTable sequence diagram
Figure 5. RemoteTable execution sequence.

Simplifications

For simplicity, the remote model does not protect against conflicting edits. Only one editor should be run at a time. (Concurrent edits can be supported by adding request IDs.)

Another simplifying decision is that the clients and server must agree beforehand on the column structure of the table. In other words, the server supplies the row data to the clients, but the clients must already know the kind of table they're dealing with. The demo clients use DefaultModelTemplate to predefine the column names and classes, and to determine which cells may be edited. (In the demo, the first two columns are not editable.)

The rest of this section describes the class structure and implementation. Read on or skip ahead to learn about the SwingWorker version used in these demos. The Downloading section explains how to download and run this demo.

Implementation

Figure 6 shows a rough diagram of the class structure.

RemoteTable class diagram
Figure 6. RemoteTable classes.

The remote model implements the RemoteTableModel interface, which looks something like an AbstractTableModel, except all the methods can throw exceptions. To jump-start a new client, the remote table model sends a full update event to the client's listener when the listener is registered. Remote table model events contain the values for the updated cells.

RemoteTableModelAdapter adapts any TableModel to a RemoteTableModel. The demo table model was lifted from The Java Tutorial and delays were inserted for simulation purposes. The RemoteTableModelAdapter uses a ReadWriteLock to control access to the adapted table model. (ReadWriteLock is also part of the util.concurrent package - see Resources.)

The RemoteTable component uses a DefaultRemoteTableModelListener to receive the callbacks from the remote model. The listener updates the local model on the event-dispatch thread. Because the remote model may report that rows have been inserted or deleted, the listener requires the local model to be a DefaultTableModel, which supports insertion and deletion.


SwingWorker Revised

The demos use SwingWorker when they need to run a time-consuming task in the background and then update the UI.

The SwingWorker used in the demos is based on the one presented in "Using a Swing Worker Thread," but has been reimplemented to fix a race condition, add timeout support, and improve the exception handling.

The new implementation is based on the FutureResult class from Doug Lea's util.concurrent package (see Resources). By relying on FutureResult for most of the work, the SwingWorker implementation is simple and flexible.

The rest of this section describes the implementation in more detail. Read on or skip ahead to download the source.

Runnable FutureResult

A FutureResult, as its name suggests, is a holder for the result of an action. It is designed to be used with a Callable, which is a runnable action that returns a result:

public interface Callable {
  Object call() throws Exception;
}

The new SwingWorker is a Runnable FutureResult. When run, it sets the result to the value returned by construct(), and then invokes finished() on the event-dispatch thread. (Note: SwingWorker is an abstract class; you subclass it to implement construct() and finished().)

Here's the code from SwingWorker's run() method:

  Callable function = new Callable() {
    public Object call() throws Exception {
      return construct();
    }
  };

  Runnable doFinished = new Runnable() {
    public void run() {
      finished();
    }
  };

  setter(function).run();
  SwingUtilities.invokeLater(doFinished);

The first statement converts construct() into a Callable function, and the second statement converts finished() to doFinished, a Runnable. Then setter(function) is run, and doFinished is invoked.

setter(function)

The missing piece is setter(function). It creates an ironclad Runnable that, when run, calls function and sets the result to the value returned. Here's the code from FutureResult:

  public Runnable setter(final Callable function) {
    return new Runnable() {
      public void run() {
        try {
          set(function.call());
        }
        catch(Throwable ex) {
          setException(ex);
        }
      }
    };
  }

Note the try-catch armor. If construct() throws anything (Exception, Error, whatever), it will be caught and recorded.

Don't race: construct first, then start

Call start() to start the worker thread. That's an important difference between this SwingWorker and the original.

Originally, the SwingWorker() constructor started the thread automatically, but this created a dangerous race between the thread and the subclass constructor: when the thread is started in SwingWorker(), the subclass constructor has not yet completed. Instead, construct SwingWorker first and then call start().

By the way, RemoteTable does not call start(). Rather, the SwingWorker is executed as a Runnable by the QueuedExecutor.

Timeout support

The new SwingWorker supports a timeout, which is enabled by overriding getTimeout() to return a non-zero value. When the timeout expires, the worker thread is interrupted.

For an example, see the commented-out getTimeout() method and the TimeoutException handling in DynamicTree.

Timeouts are implemented using TimedCallable, which uses FutureResult's timedGet() method.

Improved exception handling

Anything thrown by construct() is caught and recorded. Barring an infinite loop or a deadlock, this ensures that the SwingWorker will eventually become ready. That is, it will either hold a valid result, or it will hold an exception.

The get() method retrieves the result. It's inherited from FutureResult:

  public Object get()
    throws InvocationTargetException, InterruptedException

The get() method will throw InvocationTargetException if construct() threw an Exception. The actual exception thrown by construct() can be retrieved by calling getTargetException().

The get() method will throw InterruptedException if the getting thread is interrupted while waiting for the result - but this rarely happens with a SwingWorker because the getting thread is usually the event-dispatch thread, and the result will always be ready before finished() is ever invoked.

More invoke utilities

The SwingWorker implementation is in the jozart.swingutils package. In this package you will also find InvokeUtils, which provides a few more invokeXXX() methods. Background threads may use these methods to get a value, or user input, on the event-dispatch thread and return the result to the background thread.


Downloading

The source-code files for all the demos, along with class files and resources, are contained in the zip file: threads3 demos.zip

The threads3_demos.zip file contains:

  • jozart/
    • dynamictree/ - DynamicTree demo.
    • remotetable/ - RemoteTable bean source.
    • remotetabledemo/ - RemoteTable demo.
    • swingutils/ - SwingWorker.
  • EDU/ - util.concurrent package (only referenced classes).
  • java.policy - RMI security policy.
  • remotetable.jar - RemoteTable bean (for IDEs).

NOTE: The util.concurrent classes are from v1.2.3. Only those classes used by the demos are included.

NOTE: Java 2 is required to run the demos. (The util.concurrent package uses Collections in a few places.)

To run the demos, first unzip threads3_demos.zip into an empty folder, being careful to preserve the folder names. Then change to the folder where you unzipped the files.

Running DynamicTree

To run DynamicTree as an applet:

  > appletviewer jozart/dynamictree/DynamicTree.html

To run DynamicTree as an application:

  > java jozart.dynamictree.DynamicTree

Running RemoteTableDemo

The remote table server and clients are designed to be run separately using RMI. But RMI can be tricky to set up, requiring on an RMI registry, an HTTP server, and a security policy file. Fortunately, there's another demo with fewer dependencies. I'll explain how to run it first.

RemoteTableDemo is an editor, a viewer, and a server all rolled into one.

To run RemoteTableDemo as an applet:

  > appletviewer jozart/remotetabledemo/RemoteTableDemo.html

To run RemoteTableDemo as an application:

  > java jozart.remotetabledemo.RemoteTableDemo

Running server and clients separately

To run the remote table server and clients separately, perform the following steps:

1. Make sure the rmiregistry is running:

  > start rmiregistry

2. Make sure an http server is running:

  > start httpd

3. Change to the demo directory.

  > cd threads3

4. Start the server and clients:

  > java -Djava.security.policy=java.policy
      -Djava.rmi.server.codebase=http://host/threads3/
      jozart.remotetabledemo.RemoteTableServer
  > java -Djava.security.policy=java.policy
      -Djava.rmi.server.codebase=http://host/threads3/
      jozart.remotetabledemo.RemoteTableEditor
  > java -Djava.security.policy=java.policy
      -Djava.rmi.server.codebase=http://host/threads3/
      jozart.remotetabledemo.RemoteTableViewer

You'll need to supply your hostname in the codebase setting. You should also check that java.policy isn't granting something you'd rather it didn't.

There's more info about RMI in The Java Tutorial (see Resources).


Conclusion

This article demonstrated two methods for using threads with model-based components like JTable and JTree. A revised SwingWorker utility was also presented.

In the introduction, I stated that building thread safe components was hard. Java provides language-level support for threads and locks, but that doesn't suddenly make concurrent programming easy. Swing's single-thread rule shields programmers from the complexity of thread safety in everyday use, but imposes a penalty when threads are needed.

To make concurrent programming easier, for myself and for future users of my code, I've used packaged utilities, proven design patterns, and established convention as much as possible.

In closing, I offer this advice to anyone interested in extending SwingWorker or developing their own thread utilities: Know thy memory model.

In addition to controlling access to shared resources, Java's synchronized statement also ensures the transmission of values between threads. This important detail is often overlooked by developers. More information about the Java Memory Model is available in Doug Lea's online supplement to Concurrent Programming in Java. Links to this and other resources are listed in the next section.


Resources

  1. Threads and Swing - Hans Muller and Kathy Walrath.
  2. Using a Swing Worker Thread - Hans Muller and Kathy Walrath.
  3. Understanding the TreeModel - Eric Armstrong, Tom Santos, Steve Wilson.
  4. The JTable Class is DB-Aware - Philip Milne and Mark Andrews.
  5. The Java Tutorial on RMI - Ann Wollrath and Jim Waldo.
  6. Concurrent Programming in Java - Doug Lea.
  7. util.concurrent package - Doug Lea.

About the author

Joseph Bowbeer is a Java consultant living in Seattle. His fascination with concurrent programming began the day his cursor stopped blinking. He thanks Doug Lea, Tom May, David Holmes, and Joshua Bloch for sharing their knowledge. Any errors and omissions are his own doing.

 

[an error occurred while processing this directive]