Welcome!

Java Authors: App Man, Liz McMillan, Jeremy Geelan, Yakov Fain, Hari Gottipati

Related Topics: Java

Java: Article

Building Thread-Safe GUIs With Swing

Building Thread-Safe GUIs With Swing

Remember the old axiom, Be careful what you ask for, you just might get it? That's what happened with the Abstract Windowing Toolkit (AWT), GUI controls, and threading. Developers were tired of always worrying about multithreaded access to GUI elements, so it sounded like a good idea to create an application framework that was always thread-safe.

What do we mean by thread-safe? Two separate threads of execution can access the control at the same time without the developer having to worry about the threads interfering with one another. AWT made this possible...and was consequently very sluggish. The original designers of Java built a lot of thread safety into the language and its libraries. For example, the collections classes from the original JDK (Vector and Hashtable) are always thread-safe. However, that safety comes at a cost. Because there's a great deal of overhead necessary to build thread-safe artifacts, they tend to be much slower than nonthread-safe alternatives. This is true of the collections classes (which is why we now have ArrayList and Hashmap, the nonthread-safe alternatives) and Swing.

When it came time to build JFC and Swing, one of the design decisions was that thread safety would be eschewed in favor of speed. This doesn't mean the controls can never be accessed from multiple threads, but the developer is now responsible for adding code to ensure that no ill effects occur. This article shows how to build thread-safe GUIs in Swing. First, however, I'll show what happens if you don't take care of threading.

Thread Collisions
Consider the application shown in Figure 1. It's a simple list box whose items are updated via a thread. For the first version of this example, no thread safety is built into the code (see Listing 1).

As you can see, the thread updates the contents of the list box continuously. What's bad about this? When it runs, you get the result shown in Figure 1. As the thread updates the list box, the drawing thread in Swing also accesses the elements to keep the GUI representation updated. Because both threads can access the content at the same time, the worker thread can pull items off the content at the same time the drawing thread is accessing them for display. This causes the cascading series of exceptions you see in the figure.

How can this be prevented? By using one of the static methods in the EventQueue class - invokeLater() or invokeAndWait(). These methods were originally in the SwingUtilities class (in JDK 1.1.x) and are now accessible from either class. These methods can take a thread as their parameter and are responsible for executing the thread that's passed to them in sync with the main Swing thread. These two methods determine how you want the update to occur. The invokeLater() method returns immediately, placing the update code in the regular event queue of Swing. This means the updating will take place as soon as possible, but it doesn't make your code wait for the update to occur. There may be situations in which you want your code to wait until the update has occurred. The invokeAndWait() method won't return until the update is complete. These methods are the secret to handling graceful threaded Swing code.

Listing 2 demonstrates how to solve the original problem. I've added code to call invokeLater() to the code that must update the Swing control's contents. Now when the application runs, no exceptions occur because both the update and drawing threads are no longer in conflict. The pertinent change to the code appears in the body of the run() method of the WorkerThread class and is shown here:

EventQueue.invokeLater(new Runnable() {
public void run() {
if (model.contains(i))
model.removeElement(i);
else
model.addElement(i);
}
});
This is a common technique for updating Swing controls from within a thread. A new anonymous class that implements the Runnable interface is created. The code in the run() method is the code placed in the main event thread in Swing. Notice that this is the same code used in the previous example to update the list box's model - the difference here is the call to EventQueue.invokeLater().

Progress for Long-Running Processes
Here's a practical example of the need to have a thread update in real time. A common chore in applications is to show the users the progress of some long-running process. There are two ways to create such a process. If coded directly into the application (i.e., not in a thread), the application can't show progress because the process hasn't finished yet. In other words, if you do the work in an event handler, you're occupying the main event thread. If the process was spawned in a thread, the thread safety of the GUI must be considered. The second solution is the only real choice if you want to provide feedback, and armed with the EventQueue methods listed above, it's easy to accommodate.

For this sample application I copy a collection of files from one location to another. This is a classic example of a process for which you want to provide feedback. This application copies all the source files from the Java SwingSet2 demo to the temp directory (hey, I had to copy something from somewhere to somewhere else, didn't I?). As you can see in Figure 2, the user interface provides feedback on the percentage of completion for the two tasks. First, the application deletes the files from the target directory (if they've been previously copied there) and then copies the source files to the target.

Because there are two threads with common traits at work in this application, I first declared an abstract thread class to encapsulate the similarities. This class appears in Listing 3. Two characteristics are required from each thread. First, they must have a reference to the frame class to access the UI widgets to notify them of the progress. Second, a terminate request flag will appear in this base class. As you probably know, the thread stop() method has been deprecated in Java 2 because it can lead to undesirable situations. Now, to be able to stop a thread, you must provide a suicide watch flag - not so much a command to stop as a request that the thread commit suicide. Both worker threads subclass the abstract WorkerThread class.

The delete thread appears in Listing 4. One item to note in both threads is the manner in which they interact with the frame class. The thread doesn't directly access the JProgressBar on the frame. Instead, frame methods are called to handle the actual initialization and updating. The frame's initCopyProgress() method is shown here:

void initCopyProgress(int numFiles) {
jprgrsbrCopy.setMaximum(numFiles);
jprgrsbrCopy.setMinimum(0);
}
The updateCopyProgress() method handles the updating of the progress bar's progress.
void updateCopyProgress() {
jprgrsbrCopy.setValue(
jprgrsbrCopy.getValue() + 1);
}

Making the frame's methods update the progress bar is a good idea so the thread doesn't have too much knowledge of how the user interface is handling the progress mechanism. The UI could now change to incorporate a gauge or some other progress technique without affecting the threads. The remainder of the DeleteThread deletes the files in the target directory. Notice the call to EventQueue.invokeLater() to update the frame. The last act of the DeleteThread is to instantiate and call the CopyThread (see Listing 5). Both threads could have been created at the same time and the CopyThread made to wait on the DeleteThread. However, because these two operations must run serially, it makes sense to spawn one from the other.

The CopyThread performs many of the same housekeeping operations as the DeleteThread. Most of the code in the CopyThread concerns itself with copying files, which is not relevant to this discussion. For our purposes note the call to update the user interface in a call to EventQueue. invokeLater().

Building user interfaces that gracefully spawn threads and in turn report progress along the way is easy once you understand the requirements built into the Swing application framework. Now we have a rich user interface library that offers better speed and capabilities than the old one. When necessary, the developer can make it thread-safe to give it the capabilities of the old AWT framework without the disadvantages.

More Stories By Neal Ford

Neal is the Vice-President of Technology at
the DSW Group. He is also the designer and
developer of applications, instructional
materials, magazine articles, video presentations,
and author of the books Developing with Delphi:
Object-Oriented Techniques and JBuilder 3
Unleashed. Neal has also been the featured
speaker for the Borland Delphi/C++Builder/JBuilder
World Tours and has spoken extensively at annual
Borland Developers Conferences worldwide.
He can be reached at nford@thedswgroup.com
or by calling The DSW Group at 800-356-9644.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.