Thread-Unsafe Classes

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.

Explicit Synchronization

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.

Explicit synchronization and native code

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.

Single-Thread Access

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

Using the Java Foundation Classes

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.

The event-dispatching thread and event-related method

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 invokeAndWait() method

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:

  1. The applet will initialize itself (via the init() method), creating a GUI with a single element (a slider).

  2. In the applet’s start() method, a calculation thread is spawned.

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

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

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

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

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.

Other thread-unsafe classes

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset