Swing and multithreading

  David Geary is an independent Java consultant. He is the author of Graphic Java—Mastering the JFC. He can be contacted at [email protected]
Note: Information in this article is based on the 1.1 beta release of Swing.

FOR THE MOST part, Swing is not thread-safe, meaning that Swing components can only be accessed from a single thread. First, we'll discuss why Swing is not thread safe and then we'll discuss the ramifications a single-threaded design has for developers using Swing.

WHY SWING IS NOT THREAD-SAFE
Developing multithreaded applications is not easy, even in Java. Designing a thread-safe toolkit is no simple matter; for example, determining how to synchronize access to a class can be a complex undertaking (see Lea, D., Concurrent Programming in Java, Addison-Wesley, Reading, MA, 1997). Likewise, extending thread-safe classes is somewhat of an art of its own and can be fraught with peril when undertaken by developers who are not experts at threads programming, which includes most developers. One of the main reasons that Swing is not thread safe is to simplify the task of extending its components.

Another reason that Swing is not thread-safe is due to the overhead involved in obtaining and releasing locks and restoring state. All applications that use a thread-safe GUI toolkit, whether they are multithreaded or not, must pay the same performance penalty incurred by a thread-safe toolkit.

The use of threads increases the difficulty associated with debugging, testing, maintenance, and extensibility. For example, testing and maintenance, which are normally arduous tasks become even more so for most multithreaded applications.

Some Swing component methods do support multithreaded access. For example, the JComponent methods repaint, revalidate, and invalidate all queue requests, which are placed on the event dispatch thread and therefore may be called from any thread. Also, event listener lists can have listeners added and removed from multiple threads. Finally, some component methods are synchronized; for example, the JCheckBoxMenuItem.setState() is synchronized and therefore may be invoked from multiple threads.

Ramifications of Swing's Single-Threaded Design
The main consequence of Swing's single-threaded design is as follows: Swing components can only be accessed from the event dispatch thread once a component is available for painting onscreen.

The event dispatch thread is the thread that invokes callback methods such as paint and update in addition to event handler methods defined in event listener interfaces. For example, implementors of the ActionListener and PropertyListener interfaces have their actionPerformed and propertyChange methods invoked on the event dispatch thread.

Technically, Swing components can be accessed from multiple threads before the component's peer has been created, meaning that the component is available for painting onscreen. This means for instance, that components can be constructed and manipulated in an applet's init method as long as the components are not made visible before they are manipulated.

SwingUtilities.invokeLater AND invokeAndWait METHODS
Because the AWT and, therefore, Swing are event driven toolkits, it is natural to implement the updating of a visible GUI in callback methods. For example, if a list of items needs to be updated when a button is activated, the updating of the list is typically implemented in the actionPerformed method of an event listener attached to the button.

Sometimes, however, it may be necessary to update Swing components from a thread other than the event dispatch thread. For instance, if the list of items mentioned above is populated with data obtained from a database or over the Internet, there may be a perceptible delay from the time the button is activated until the list is updated. If the information retrieval is implemented within the actionPerformed method, the button will remain painted in its pressed state until the call to actionPerformed returns. Not only will it take a while for the button to pop up, but in general, lengthy operations should not be performed in event handler methods because other events cannot be dispatched until the event handler method in question returns.

At times, it may be better to retrieve information or perform other time-consuming operations on a separate thread, which allows the user interface to update immediately and frees the event dispatch thread to dispatch other events. Fortunately, Swing offers two mechanisms that supports such a scenario.

The SwingUtilities class provides two methods: invokeLater and invokeAndWait that queue a runnable object on the event dispatch thread. When the runnable object makes its way to the front of the event dispatch queue, its run method is invoked. In effect, this allows an arbitrary block of code from another thread to be invoked from the event dispatch thread.

SwingUtilities.invokeLater
Before illustrating the invokeLater and invokeAndWait methods, let's first look at an applet that misbehaves by updating a Swing component on a thread other than the event dispatch thread. The applet shown in Figure 1 contains a button and a progress bar. When the button is activated, some lengthy operation to retrieve information is simulated. Once the information (an integer value) is retrieved, it is used to update the applet's progress bar.

From the Pages
Figure 1. Updating Swing components from multiple threads.

The picture on the left in Figure 1 shows the applet in its initial state. The picture on the right shows the applet after the start button has been activated, information has been retrieved, and the progress bar has been updated.

The applet adds an action listener to the button, and the listener creates a new thread that continuously retrieves information and updates the progress bar. The information retrieval is simulated by sleeping for half a second, and the thread obtains a reference to the progress bar from the applet itself (see Listing 1).

After the button's listener starts the thread, it sets the enabled state of the button to false. This is a valid operation, because the actionPerformed method is invoked on the event dispatch thread. However, setting the value of the progress bar in the GetInfoThread is a risky proposition, because the progress bar is being updated from a thread other than the event dispatch thread.

The applet shown in Figure 1 is listed in its entirety in Listing 2.

The correct way to update the progress bar in the applet shown in Figure 1 is to use SwingUtilities.invokeLater() (or alternatively, invokeAndWait()). The constructor for the GetInfoThread class (see Listing 3) is modified to instantiate a runnable object that obtains a reference to the progress bar from the applet and updates its value. The run method of the GetInfoThread class invokes SwingUtilities.invokeLater and passes a reference to the runnable object. Note that SwingUtilities.invokeLater() may throw an InterruptedException, so the exception must be caught whenever invokeLater() is invoked.

A revised version of the applet shown in Figure 1 is listed in its entirety in Listing 4.

SwingUtilities.invokeAndWait
SwingUtilities.invokeAndWait(), like invokeLater(), queues a runnable object on the event dispatch thread. However, whereas invokeLater() returns immediately after it has queued the runnable object, invokeAndWait() waits until the run method of the runnable object has been started before returning. The invokeAndWait method is useful when information must be retrieved from a component before another operation can be performed on another thread.

For example, the applet listed in Listing 4 updates the value of the progress bar regardless of whether the new value is the same as the current value of the progress bar. It would be ever-so-slightly more efficient if the progress bar's value were only updated when the new value differs from the current value of the progress bar. Modifying the applet to do so will afford us the opportunity to investigate the invokeAndWait method.

First, the GetInfoThread class is modified to create two runnable objects—one that gets the current value of the progress bar, and another that sets the value of the progress bar (see Listing 5).

Next, the run method of the GetInfoThread class is modified to obtain the current value of the progress bar by using invokeAndWait() (see Listing 6). SwingUtilities.invokeAndWait() is used to obtain the current value of the progress bar, and invokeLater() is used to set the value of the progress bar. The call to InvokeAndWait will not return until the run method of the getValue runnable object returns.

SwingUtilities.invokeAndWait() may throw one of two exceptions: either an InterruptedException, or an InvocationTargetException. The exceptions must be caught whenever invokeAndWait() is used or the method in which the call is made must have a throws clause.

The complete revised applet is listed in Listing 7.

One important distinction between invokeLater() and invokeAndWait() is that invokeLater() can be called from the event dispatch thread, whereas invokeAndWait() cannot. The problem with calling invokeAndWait() from the event dispatch thread is that invokeAndWait() blocks the thread that it is called from until the runnable object is dispatched from the event dispatch thread and its run method invoked. If invokeAndWait() is called from the event dispatch thread, thread deadlock will occur because invokeAndWait() is waiting for events to be dispatched, but because it is called from the event dispatch thread, events cannot be dispatched until invokeAndWait() returns.

About the Author

David Geary was the lead engineer for the Java Management API GUI toolkit, and is now an independent Java consultant. David is the author of Graphic Java—Mastering the JFC. He can be contacted at [email protected].