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.
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.