In a perfect world, we would not have to write this section: in that world, every class that you used would be correctly synchronized for use by multiple threads running simultaneously, and you would be free from considering synchronization issues whenever you used someone else’s Java classes.
Welcome to the real world. In this world, there are often times when you need to use classes that are thread unsafe—classes that lack the correct synchronization to be used by multiple threads. Just because we acknowledge that these circumstances exist does not mean that you are absolved from producing thread-safe classes in your own work: we urge you to make this a better world and correctly synchronize all of your own classes.
In this section, we’ll examine two techniques that allow you to deal with classes that are not thread safe.
Since its inception, Java has had certain classes that are collection classes: the Hashtable class, the Vector class, and others provide aggregates of objects. These classes all have the advantage that they are thread safe: their methods contain the necessary synchronization such that two threads that simultaneously insert objects into a vector, for example, will do so without corrupting the internal state of the vector.
Java 2 formalized the notion of a collection by introducing a number of collection classes; these are classes that implement either the Collection or the Map interface. There are a number of these classes: the HashMap and ArrayList classes, for example, provide similar semantics to the original Hashtable and Vector classes. But there is a big difference: most of the new collection classes are not thread safe.
In fact, there is no rule about these classes: while most of them are not thread safe, some of them are (such as the original Hashtable class, which implements the Map interface). And most of the thread-unsafe classes have the capability of providing a thread-safe implementation, so that when you deal with an object that is only identified by a generic type (such as Map), you are unsure as to whether the object in question is thread safe.
This all places a big burden on the developer, who must now figure out whether a particular Map object is thread safe, and, if not, must then ensure that the object is used correctly when multiple threads are present. The easiest way to do this is simply to explicitly synchronize all access to the object:
import java.util.*; public class ArrayTest { private ArrayList al; public ArrayTest() { al = new ArrayList(); } public void addItems(Object first, Object second) { synchronized(al) { al.add(first); al.add(second); } } public Object get(int index) { synchronized(al) { return al.get(index); } } }
All accesses to the array list in this example are synchronized; now
multiple threads can call the addItems()
and
get()
methods of the ArrayTest without damaging
the internal state of the array list.
Note that we’ve made the array list itself private. In order for this technique to work, we have to ensure that no one inadvertently uses the array list without synchronizing it, and the simplest way to do that is to hide the actual array list within the object that uses it. That way, we only have to worry about accesses to the array list from within our ArrayTest class.
The addItems()
method shows one advantage of
providing the collection classes as they are: we can add multiple
items to the collection within a single synchronization block. This
is more efficient than synchronizing the add()
method of the ArrayList class. In our test class, we need only obtain
the synchronization lock once; in the traditional Vector class,
we’d have to obtain the synchronization lock twice. This
efficiency comes at a high price, however: if you forget to
synchronize the map correctly, you’ll end up with a nasty race
condition that will be very hard to track down. Which side you land
on in this debate is a matter of personal preference.
This technique can be used with any thread-unsafe class provided that all accesses to the thread-unsafe objects are synchronized as we’ve shown. There are some thread-unsafe classes (such as the JFC [Swing] classes, which we’ll look at later) for which this technique will not work, since those classes internally call other thread-unsafe classes and do not synchronize access internally to those unsafe objects. But for unsafe data structure classes, explicit synchronization is the technique to use.
You must use explicit synchronization when you need to call a native library that is not thread safe. This may be a frequent occurrence, since developers who use C or other programming languages often do not consider that their libraries may be used in a threaded environment.
However, there is a slight difference in this case. We cannot simply synchronize at the object level (as we did in the previous example), because every object is sharing the same native code: there is only one instance of the shared native library that is loaded into the virtual machine. Hence, we must synchronize at the class level, so that every object that uses the native library will share the same lock.
It’s simple to perform this task:
public class AccessNative { static { System.loadLibrary("myLibrary"); } public static synchronized native void function1(); public static synchronized native void function2(); ... }
Here we simply make each method that calls into the native library both static and synchronized. This ensures that only one thread in the virtual machine can enter the native methods at any point in time, since they all would have to acquire the single lock associated with the AccessNative class.
There is one caveat here: if another class also loads the myLibrary library, threads executing objects of that class will be able to call into the same native library code concurrent with the threads executing methods of the AccessNative class.
This technique is similar to one that was used by the JDBC-ODBC bridge: in early versions of the bridge, it was assumed that the underlying ODBC drivers were not thread safe, and so the bridge serialized access to the native library. This greatly reduced the utility of the bridge, however, since threads could not concurrently access the database—which is a problem for most database applications, where threads that access the database are often blocked waiting for I/O.
In Java 2, versions of the JDBC-ODBC bridge now assume that the underlying ODBC driver is thread safe. If you have a thread-unsafe ODBC driver, it is your responsibility to make sure that access to the driver is synchronized correctly. This is easily achieved using a modification of the first technique that we examined: simply make sure that any access to the Connection object of the driver is synchronized. In this case, however, since you are dealing with native code, you must also ensure that only one Connection object that uses the ODBC driver is present within the virtual machine.
The other technique to use with thread-unsafe classes is to ensure that only one thread ever accesses those classes. This is generally a harder task, but it has the advantage that it always works, no matter what those classes might do internally. This technique must be used whenever threads are present in a program that uses the Java Foundation Classes for its GUI. We’ll first show you how to interact with the JFC specifically, and then generalize how that technique might be used with other classes (particularly with classes that you develop).
The
Java Foundation Classes are the largest
set of classes in the Java platform, and they also bear the
distinction of being one of the few sets of classes that are not
thread safe. Hence, whenever these classes are used, we must take
care that we access JFC objects only from one thread; in particular,
we must ensure that we access JFC objects only from the
event-dispatching thread of the virtual machine. This is the thread
that executes any of the listener methods (such as
actionPerformed()
) in response to events from
the user.
All JFC objects are thread unsafe, which means that if we have our own thread that wants to invoke a method on such an object, it cannot do so directly. A thread that attempts to read the value of a slider, for example, cannot do so directly, since as it is reading the value of the slider, the user might be simultaneously changing the value of the slider. Since access to the slider is not synchronized, both threads might access the internal slider code at the same time, corrupting the internal state of the slider and causing an error. Hence, our own thread must arrange for the event-dispatching thread of the virtual machine to read the value of the slider and pass that data back to the thread.
This example also illustrates why the previous technique of explicitly synchronizing access to objects will not work for JFC: our thread could synchronize access to the slider, but the event-processing thread does not synchronize its internal access. Remember that locks are cooperative; if all threads do not attempt to acquire the lock, then race conditions can still occur.
So the requirement to interact safely with Swing components is to access them only from the event-dispatching thread; since that effectively makes access to those components single-threaded, there will be no race conditions. JFC contains many methods that are executed by the event-dispatching thread:
Methods of the listener interfaces in the
java.awt.event
package when those methods are
called from the event-dispatching thread
invokeAndWait()
invokeLater()
repaint()
We’ll look at each of these in turn.
First, let’s delve into what we mean by the event-dispatching thread. When the Java virtual machine begins execution, it starts an initial thread. Later, when the first AWT-related class (including a JFC class) is instantiated, the GUI toolkit inside of the JVM is initialized. Depending on the underlying operating system, this creates one or more additional threads that are responsible for interacting with the native windowing system.
Regardless of the number of threads created, one of these threads is known as the event-dispatching thread. This thread is responsible for getting events from the user; when the user types a character, the event-dispatcher thread receives this event from the underlying windowing system. When the user moves the mouse or presses a mouse button, the event-dispatching thread receives that event as well. When it receives an event, it begins the process of dispatching that event: it figures out which AWT component the event occurred on and calls the event methods that are registered on that component.
So any method that is called in response to one of these events will
be called in the event-dispatching thread. In normal circumstances,
any of the event-related
methods—actionPerformed()
,
focusGained()
,
itemStateChanged()
, and any other method that is
part of one of the listener interfaces in the
java.awt.event
package—will be called by the
event-dispatching thread.
That’s good news, since it means that most of the code that
needs to access Swing components will already be called in the
event-dispatching thread. So for most GUI code, you do not need to
use one of the other methods in our list: you only need to use the
invokeAndWait()
or
invokeLater()
methods if you want to access Swing
components from a thread other than the event-dispatching thread. In
other words, if you add your own thread to a Swing-based program and
that additional thread directly accesses a Swing component, you need
to use either the invokeAndWait()
or
invokeLater()
methods. Otherwise, you just write
your event-related methods as you normally would.
There are two subtle points to make about event dispatching. The
first is that methods of the JApplet class that seem to be
event-related are not called in the event-dispatching thread. In
particular, the start()
and
stop()
methods of the JApplet class are called by
another thread in the program, and you should not directly access any
Swing components in these methods. This warning technically applies
to the init()
method as well. Since the
init()
method typically does make Swing calls
(e.g., to the add()
method), that might seem like
an ominous development. However, browsers are responsible for calling
the init()
method only once, and for calling it in
a manner in which the Swing classes can be used safely. If you write
your own application that uses an instance of a JApplet within it,
you must take care to do the same thing: do not call the
show()
method of any JFrame before you call the
init()
method of the JApplet class (or use the
invokeAndWait()
method to ensure that the
init()
method is itself run in the
event-dispatching thread). And, of course, if your program calls the
init()
method, it should take care to ensure that
it does so from the event-dispatching thread.
The second point is more complicated, and it stems from the fact that
it is possible to call an event-related
method from a thread other than the event-dispatching thread.
Let’s say that you have a thread in which a socket is reading
data from a data feed; the socket gets an I/O error, and now you want
to shut down the program. You might be tempted in this case to call
the same actionPerformed()
method that is called
in response to the user selecting the button labeled
“Close”—after all, that method has the necessary
logic to shut the program down, and you wouldn’t want to
rewrite that logic. So in this case, the
actionPerformed()
method can be called by two
different threads: the event-dispatching thread (in response to a
user event) and the socket-reading thread (in response to an I/O
error). To accommodate both threads, you must make access to any
Swing components in the actionPerformed()
method
safe by using one of the invoke methods that we’ll discuss
next.
The point is that there’s nothing inherent within the
actionPerformed()
method (or any other
event-related method) that makes it safe to manipulate Swing
components: either the method is being executed by the
event-dispatching thread itself (safe), or it is being executed by
another thread (not safe). The thread context determines whether or
not it is safe to directly manipulate a Swing component, not the
method itself.
The easiest
way to ensure that access to Swing components occurs in the
event-dispatching thread is to use the
invokeAndWait()
method. When a thread executes the
invokeAndWait()
method, it asks the
event-dispatching thread to execute certain code, and the thread
blocks until that code has been executed.
Let’s see an example of this. The
invokeAndWait()
method is often used when a
thread needs to get the value of certain items within the GUI. In the
following code, we use the invokeAndWait()
method to get the value of the slider:
import javax.swing.*; import java.awt.*; public class SwingTest extends JApplet { JSlider slider; int val; class SwingCalcThread extends Thread { public void run() { Runnable getVal = new Runnable() { public void run() { val = slider.getValue(); } }; for (int i = 0; i < 10; i++) { try { Thread.sleep(2000); SwingUtilities.invokeAndWait(getVal); System.out.println("Value is " + val); } catch (Exception e) {} } } } public void init() { slider = new JSlider(); getContentPane().setLayout(new BorderLayout()); getContentPane().add("North", slider); } public void start() { new SwingCalcThread().start(); } }
While simply the skeleton of a real program, this applet puts up a slider and then starts a secondary thread to perform a calculation. Let’s look at how execution of this applet will proceed:
The applet will initialize itself (via the
init()
method), creating a GUI with a single
element (a slider).
In the applet’s start()
method, a
calculation thread is spawned.
The calculation thread will then begin executing (okay, it’s
just sleeping, but it could be doing something useful here).
Periodically, the calculation thread needs to obtain the current
setting of the slider. It does this by creating a runnable object
(the getVal
instance variable) and passing that
object to the invokeAndWait()
method. The
calculation thread then blocks until the
invokeAndWait()
method returns.
Meanwhile, the invokeAndWait()
method itself has
arranged for the run()
method of the
get
object to be invoked in the
event-dispatching thread of the GUI. When that
run()
method is invoked, the value of the slider
is stored into the val
instance variable.
Once the run()
method of the
getVal
object has returned, the
invokeAndWait()
method will return and the
calculation thread can continue its next iteration.
There’s a further complication here, however: you cannot call
the invokeAndWait()
method from the
event-dispatching thread itself; doing so will cause an error to be
thrown. If you want to execute the same code from an event callback
method and from a user thread—e.g., the socket example we
described a little earlier—then you cannot simply put all
references to Swing components inside of a call to the
invokeAndWait()
method in the
actionPerformed()
method; you must instead use the
SwingUtilities.isEventDispatchThread()
method to
see if you’re in the event dispatch method and code the
actionPerformed()
method accordingly. A skeleton
of this example would look like this:
public class TestSwing extends JApplet implements ActionListener { class ReaderThread extends Thread { public void run() { try { //... read the socket, process the data ... } catch (IOException ioe) { actionPerformed(null); } } } public void init() { JButton jb = new JButton("Close"); getContentPane().add(jb); jb.addActionListener(this); } public void actionPerformed(ActionEvent ae) { class doClose implements Runnable { public void run() { //... access Swing components here ... //... This code would normally be the body ... //... of the actionPerformed method ... } }; doClose dc = new doClose(); if (SwingUtilities.isEventDispatchThread()) dc.run(); else { try { SwingUtilities.invokeAndWait(dc); } catch (Exception e) {} } } }
This restriction does not apply to the
invokeLater()
method.
The
invokeLater()
method is similar to the invokeAndWait()
method
except that it does not block. Because it does not wait for the
target object’s run()
method to complete,
this method is inappropriate for those instances when you need to
retrieve data from JFC objects. However, this method can be used to
set data within a JFC object:
import javax.swing.*; import java.awt.*; public class SwingTest extends JApplet { JSlider slider; JLabel label; int val; class SwingCalcThread extends Thread { public void run() { Runnable getVal = new Runnable() { public void run() { val = slider.getValue(); } }; Runnable setVal = new Runnable() { public void run() { label.setText("Last calc is " + val); } }; for (int i = 0; i < 10; i++) { try { Thread.sleep(2000); SwingUtilities.invokeAndWait(getVal); SwingUtilities.invokeLater(setVal); } catch (Exception e) {} } } } public void init() { slider = new JSlider(); label = new JLabel("Last calc is 0"); getContentPane().setLayout(new BorderLayout()); getContentPane().add("North", slider); getContentPane().add("Center", label); } public void start() { new SwingCalcThread().start(); } }
In this case, there’s no reason why the calculation thread
needs to wait until the data in the label is actually set; it merely
schedules the operation and then continues to calculate. There are
circumstances in which this is inappropriate. In this example, the
new value of the label will not be reflected immediately when the
invokeLater()
method is called. As a result, the
threads may be scheduled such that one iteration of the intermediate
feedback is lost to the user. But in general, the
invokeLater()
method is useful when the thread
that invokes it does not care about the results of the
run()
method.
The
repaint()
method is also a thread-safe method, even within the JFC. Hence, any
thread can at any time call the repaint()
method
of a particular component. This is very useful, since a variety of
Java applications depend on periodic repainting behavior.
The reason this works is that the repaint()
method itself doesn’t really accomplish a great deal: it merely
arranges for the paint()
method to be called by
the event-dispatching thread. Hence, an applet can have a thread that
stores data into the instance variables of the applet and then calls
the applet’s repaint()
method; when the
applet next paints itself, it will use the new data.
There are other techniques for dealing with threads and the JFC.
There is a timer class within the JFC that hides the details of the
invokeLater()
method for you; you pass an
ActionListener object to the timer and it arranges for the
actionPerformed()
method of that object to be
called from the event-dispatching thread every time the timer fires.
Additionally, there is a SwingWorker class on Sun’s web site that performs the opposite of the principles that we’ve shown here: it dispatches a new target thread and provides a way for code within the event-dispatching thread to poll the target thread for its results. In our opinion, this is backwards: how will the event-dispatching thread know when it should check for output from the worker thread? Still, if you’re interested, check out Sun’s web site for more details.
How unsafe are the Swing classes, anyway? In the
examples we’ve just shown, we’ve essentially set and
retrieved an integer—the value—from the JSlider class.
Since reading or writing an integer is guaranteed to be an atomic
action in Java, is it really necessary to use the invoke methods?
There are probably cases where the answer is no, but those cases
cannot be clearly described. So it’s really safer to use the
invoke methods to execute all Swing methods from a thread other than
the event-dispatching thread. Even in our example where we seem to be
performing a simple assignment, there’s a lot going on that
we’re not aware of: the getValue()
method
has to call the getModel()
method, and a new model
may be in the middle of being installed. That may be okay, or it may
cause the getModel()
method to return a null
object reference, which would cause a runtime exception; without a
very careful examination of the Swing code, it’s tough to be
sure. And it’s impossible to know what future implementations
might be. It’s far better just to use the invoke methods as
we’ve shown.
The implementation of the invokeAndWait()
method
(as well as the other similar methods we’ve just examined)
provides us with a clue on how to deal with other unsafe classes for
which simple external synchronization is insufficient. We need to
implement a similar mechanism for these classes.
This is typically done by establishing a queue somewhere that one
thread—and only one thread—is responsible for acting on.
The
invokeAndWait()
method itself is based on the fact that there is an existing event
queue within the virtual machine: it simply creates some new events,
posts them to the queue, and waits for the event-dispatching thread
to process them (the invokeLater()
method
returns without waiting). The event-dispatching thread is then
responsible for executing the run()
method of
the object passed to the invokeAndWait()
method.
Interestingly enough, the invokeAndWait()
method
does not create a new thread, nor does it cause one to be created:
the run()
method is executed by an existing
thread (the event-dispatching thread), just as we did in Chapter 7 with our thread pool example.
This similarity tells us how to ensure that only a single thread
accesses our unsafe classes: place all access to those classes within
objects executing in a thread pool and initialize the thread pool to
contain only a single thread. Now we can use the
addRequest()
and
addRequestAndWait()
methods of the thread pool
just as we used the invokeLater()
and
invokeAndWait()
methods
earlier.