Chapter 9. Threads

Threads allow multiple activities to proceed concurrently in the same program. Multithreaded programming is more difficult than single-threaded programming, so the advice of Item 30 is particularly applicable here: If there is a library class that can save you from doing low-level multithreaded programming, by all means use it. The java.util.Timer class is one example, and Doug Lea's util.concurrent package[Lea01] is a whole collection of high-level threading utilities. Even if you use such libraries where applicable, you'll still have to write or maintain multithreaded code from time to time. This chapter contains advice to help you write clear, correct, well-documented multithreaded programs.

Item 48: Synchronize access to shared mutable data

The synchronized keyword ensures that only a single thread will execute a statement or block at a time. Many programmers think of synchronization solely as a means of mutual exclusion, to prevent an object from being observed in an inconsistent state while it is being modified by another thread. In this view, an object is created in a consistent state (Item 13) and locked by the methods that access it. These methods observe the state and optionally cause a state transition, transforming the object from one consistent state to another. Proper use of synchronization guarantees that no method will ever observe the object in an inconsistent state.

This view is correct, but it doesn't tell the whole story. Not only does synchronization prevent a thread from observing an object in an inconsistent state, but it also ensures that objects progress from consistent state to consistent state by an orderly sequence of state transitions that appear to execute sequentially. Every thread entering a synchronized method or block sees the effects of all previous state transitions controlled by the same lock. After a thread exits the synchronized region, any thread that enters a region synchronized by the same lock sees the state transition caused by that thread, if any.

The language guarantees that reading or writing a single variable is atomic unless the variable is of type long or double. In other words, reading a variable other than a long or double is guaranteed to return a value that was stored into that variable by some thread, even if multiple threads modify the variable concurrently without synchronization.

You may hear it said that to improve performance, you should avoid the use of synchronization when reading or writing atomic data. This advice is dangerously wrong. While the atomicity guarantee ensures that a thread will not see a random value when reading atomic data, it does not guarantee that a value written by one thread will be visible to another: Synchronization is required for reliable communication between threads as well as for mutual exclusion. This is a consequence of a fairly technical aspect of the Java programming language known as the memory model [JLS, 17]. While the memory model is likely to undergo substantial revision in an upcoming release [Pugh01a], it is a near certainty that this fact will not change.

The consequences of failing to synchronize access to a shared variable can be dire even if the variable is atomically readable and writable. Consider the following serial number generation facility:

					// Broken - requires synchronization!
private static int nextSerialNumber = 0;

public static int generateSerialNumber() {
    return nextSerialNumber++;
}

The intent of this facility is to guarantee that every invocation of generateSerialNumber returns a different serial number, as long as there are no more than 232 invocations. Synchronization is not required to protect the invariants of the serial number generator because it has none; its state consists of a single atomically writable field (nextSerialNumber), and all possible values of this field are legal. However, the method does not work without synchronization. The increment operator (++) both reads and writes the nextSerialNumber field so it is not atomic. The read and write are independent operations, performed in sequence. Multiple concurrent threads can thus observe the nextSerialNumber field with the same value and return the same serial number.

More surprisingly, it is possible for one thread to call generateSerialNumber repeatedly, obtaining a sequence of serial numbers from zero to n, after which another thread calls generateSerialNumber and obtains a serial number of zero. Without synchronization, the second thread might see none of the updates made by the first. This is a result of the aforementioned memory model issue.

Fixing the generateSerialNumber method is as simple as adding the synchronized modifier to its declaration. This ensures that multiple invocations won't be interleaved and that each invocation will see the effects of all previous invocations. To bulletproof the method, it might also be wise to use long instead of int or to throw an exception if nextSerialNumber were about to wrap.

Next, consider the process of stopping a thread. While the platform provides methods for involuntarily stopping a thread, these methods are deprecated because they are inherently unsafe—their use can result in object corruption. The recommended method of stopping a thread is simply to have the thread poll some field whose value can be changed to indicate that the thread is to stop itself. The field is typically a boolean or an object reference. Because reading and writing such a field is atomic, some programmers are tempted to dispense with synchronization when accessing the field. Thus it is not uncommon to see code that looks like this:

					// Broken - requires synchronization!
public class StoppableThread extends Thread {
    private boolean stopRequested = false;

    public void run() {
        boolean done = false;

        while (!stopRequested && !done) {
            ... // do what needs to be done.
        }
    }

    public void requestStop() {
        stopRequested = true;
    }
}

The problem with this code is that in the absence of synchronization, there is no guarantee as to when, if ever, the stoppable thread will “see” a change in the the value of stopRequested that was made by another thread. As a result, the requestStop method might be completely ineffective. Unless you are running on a multiprocessor, you are unlikely to observe the problematic behavior in practice, but there are no guarantees. The straightforward way to fix the problem is simply to synchronize all access to the stopRequested field:

					// Properly synchronized
					cooperative thread termination
public class StoppableThread extends Thread {
    private boolean stopRequested = false;

    public void run() {
        boolean done = false;

        while (!stopRequested() && !done) {
            ... // do what needs to be done.
        }
    }

    public synchronized void requestStop() {
        stopRequested = true;
    }

    private synchronized boolean stopRequested() {
        return stopRequested;
    }
}

Note that the actions of each of the synchronized methods are atomic: The synchronization is being used solely for its communication effects, not for mutual exclusion. It is clear that the revised code works, and the cost of synchronizing on each iteration of the loop is unlikely to be noticeable. That said, there is a correct alternative that is slightly less verbose and whose performance may be slightly better. The synchronization may be omitted if stopRequested is declared volatile. The volatile modifier guarantees that any thread that reads a field will see the most recently written value.

The penalty for failing to synchronize access to stopRequested in the previous example is comparatively minor; the effect of the requestStop method may be delayed indefinitely. The penalty for failing to synchronize access to mutable shared data can be much more severe. Consider the double-check idiom for lazy initialization:

					// The double-check idiom for lazy
					initialization - broken!
private static Foo foo = null;

public static Foo getFoo() {
    if (foo == null) {
        synchronized (Foo.class) {
            if (foo == null)
                foo = new Foo();
        }
    }
    return foo;
}

The idea behind this idiom is that you can avoid the cost of synchronization in the common case of accessing the field (foo) after it has been initialized. Synchronization is used only to prevent multiple threads from initializing the field. The idiom does guarantee that the field will be initialized at most once and that all threads invoking getFoo will get the correct value for the object reference. Unfortunately, the object reference is not guaranteed to work properly. If a thread reads the reference without synchronization and then invokes a method on the referenced object, the method may observe the object in a partially initialized state and fail catastrophically.

That a thread can observe the lazily constructed object in a partially initialized state is wildly counterintuitive. The object is fully constructed before the reference is “published” in the field from which it is read by other threads (foo). But in the absence of synchronization, reading a “published” object reference does not guarantee that a thread will see all of the data that were stored in memory prior to the publication of the object reference. In particular, reading a published object reference does not guarantee that the reading thread will see the most recent values of the data that constitute the internals of the referenced object. In general, the double-check idiom does not work, although it does work if the shared variable contains a primitive value rather than an object reference [Pugh01b].

There are several ways to fix the problem. The easiest way is to dispense with lazy initialization entirely:

					//
					Normal static initialization (not lazy)
private static final Foo foo = new Foo();

public static Foo getFoo() {
    return foo;
}

This clearly works, and the getFoo method is as fast as it could possibly be. It does no synchronization and no computation either. As discussed in Item 37, you should write simple, clear, correct programs, leaving optimization till last, and you should optimize only if measurement shows that it is necessary. Therefore dispensing with lazy initialization is generally the best approach. If you dispense with lazy initialization, measure the cost, and find that it is prohibitive, the next best thing is to use a properly synchronized method to perform lazy initialization:

					//
					Properly synchronized lazy initialization
private static Foo foo = null;

public static synchronized Foo getFoo() {
    if (foo == null)
        foo = new Foo();
    return foo;
}

This method is guaranteed to work, but it incurs the cost of synchronization on every invocation. On modern JVM implementations, this cost is relatively small. However, if you've determined by measuring the performance of your system that you can afford neither the cost of normal initialization nor the cost of synchronizing every access, there is another option. The initialize-on-demand holder class idiom is appropriate for use when a static field is expensive to initialize and may not be needed, but will be used intensively if it is needed. This idiom is shown below:

					// The
					initialize-on-demand holder class idiom
private static class FooHolder {
    static final Foo foo = new Foo();
}

public static Foo getFoo() { return FooHolder.foo; }

The idiom takes advantage of the guarantee that a class will not be initialized until it is used [JLS, 12.4.1]. When the getFoo method is invoked for the first time, it reads the field FooHolder.foo, causing the FooHolder class to get initialized. The beauty of this idiom is that the getFoo method is not synchronized and performs only a field access, so lazy initialization adds practically nothing to the cost of access. The only shortcoming of the idiom is that it does not work for instance fields, only for static fields.

In summary, whenever multiple threads share mutable data, each thread that reads or writes the data must obtain a lock. Do not let the guarantee of atomic reads and writes deter you from performing proper synchronization. Without synchronization, there is no guarantee as to which, if any, of a thread's changes will be observed by another thread. Liveness and safety failures can result from unsynchronized data access. Such failures will be extremely difficult to reproduce. They may be timing dependent and will be highly dependent on the details of the JVM implementation and the hardware on which it is running.

The use of the volatile modifier constitutes a viable alternative to ordinary synchronization under certain circumstances, but this is an advanced technique. Furthermore, the extent of its applicability will not be known until the ongoing work on the memory model is complete.

Item 49: Avoid excessive synchronization

Item 48 warns of the dangers of insufficient synchronization. This item concerns the opposite problem. Depending on the situation, excessive synchronization can cause reduced performance, deadlock, or even nondeterministic behavior.

To avoid the risk of deadlock, never cede control to the client within a synchronized method or block. In other words, inside a synchronized region, do not invoke a public or protected method that is designed to be overridden. (Such methods are typically abstract, but occasionally they have a concrete default implementation.) From the perspective of the class containing the synchronized region, such a method is alien. The class has no knowledge of what the method does and no control over it. A client could provide an implementation of an alien method that creates another thread that calls back into the class. The newly created thread might then try to acquire the same lock held by the original thread, which would cause the newly created thread to block. If the method that created the thread waits for the thread to finish, deadlock results.

To make this concrete, consider the following class, which implements a work queue. This class allows clients to enqueue work items for asynchronous processing. The enqueue method may be invoked as often as necessary. The constructor starts a background thread that removes items from the queue in the order they were enqueued and processes them by invoking the processItem method. When the work queue is no longer needed, the client invokes the stop method to ask the thread to terminate gracefully after completing any work item in progress.

public abstract class WorkQueue {
    private final List queue = new LinkedList();
    private boolean stopped = false;

    protected WorkQueue() { new WorkerThread().start(); }

    public final void enqueue(Object workItem) {
        synchronized (queue) {
            queue.add(workItem);
            queue.notify();
        }
    }

    public final void stop()  {
        synchronized (queue) {
            stopped = true;
            queue.notify();
        }
    }
    protected abstract void processItem(Object workItem)
        throws InterruptedException;

    // Broken - invokes alien method from synchronized block!
    private class WorkerThread extends Thread {
        public void run() {
            while (true) {  // Main loop
                synchronized (queue) {
                    try {
                        while (queue.isEmpty() && !stopped)
                            queue.wait();
                    } catch (InterruptedException e) {
                        return;
                    }

                    if (stopped)
                        return;

                    Object workItem = queue.remove(0);
                    try {
                        processItem(workItem); // Lock held!
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }
        }
    }
}

To use this class, you must subclass it to provide an implementation of the abstract processItem method. For example, the following subclass prints out each work item, printing no more than one item per second, no matter how frequently items are enqueued:

class DisplayQueue extends WorkQueue {
    protected void processItem(Object workItem)
            throws InterruptedException {
        System.out.println(workItem);
        Thread.sleep(1000);
    }
}

Because the WorkQueue class invokes the abstract processItem method from within a synchronized block, it is subject to deadlock. The following subclass will cause it to deadlock by the means described above:

class DeadlockQueue extends WorkQueue {
    protected void processItem(final Object workItem)
            throws InterruptedException {
        // Create a new thread that returns workItem to queue
        Thread child = new Thread() {
            public void run() { enqueue(workItem); }
        };
        child.start();
        child.join(); // Deadlock!
    }
}

This example is contrived because there is no reason for the processItem method to create a background thread, but the problem is real. Invoking externally provided methods from within synchronized blocks has caused many deadlocks in real systems such as GUI toolkits. Luckily it is easy to fix the problem. Simply move the method invocation outside of the synchronized block, as shown:

					// Alien method outside synchronized block - "Open call"
private class WorkerThread extends Thread {
    public void run() {
        while (true) {  // Main loop
            Object workItem = null;
            synchronized (queue) {
                try {
                    while (queue.isEmpty() && !stopped)
                        queue.wait();
                } catch (InterruptedException e) {
                    return;
                }
                if (stopped)
                    return;
                workItem = queue.remove(0);
            }
            try {
                processItem(workItem); // No lock held
            } catch (InterruptedException e) {
                return;
            }
        }
    }
}

An alien method invoked outside of a synchronized region is known as an open call [Lea00, 2.4.1.3]. Besides preventing deadlocks, open calls can greatly increase concurrency. An alien method might run for an arbitrarily long period, during which time other threads would unnecessarily be denied access to the shared object if the alien method were invoked inside the synchronized region.

As a rule, you should do as little work as possible inside synchronized regions. Obtain the lock, examine the shared data, transform the data as necessary, and drop the lock. If you must perform some time-consuming activity, find a way to move the activity out of the synchronized region.

Invoking an alien method from within a synchronized region can cause failures more severe than deadlocks if the alien method is invoked while the invariants protected by the synchronized region are temporarily invalid. (This cannot happen in the broken work queue example because the queue is in a consistent state when processItem is invoked.) Such failures do not involve the creation of a new thread from within the alien method; they occur when the alien method itself calls back in to the faulty class. Because locks in the Java programming language are recursive, such calls won't deadlock as they would if they were made by another thread. The calling thread already holds the lock, so the thread will succeed when it tries to acquire the lock a second time, even though there is another conceptually unrelated operation in progress on the data protected by the lock. The consequences of such a failure can be catastrophic; in essence, the lock has failed to do its job. Recursive locks simplify the construction of multithreaded object-oriented programs, but they can turn liveness failures into safety failures.

The first part of this item was about concurrency problems. Now we turn our attention to performance. While the cost of synchronization has plummeted since the early days of the Java platform, it will never vanish entirely. If a frequently used operation is synchronized unnecessarily, it can have significant impact on performance. For example, consider the classes StringBuffer and BufferedInputStream. These classes are thread-safe (Item 52) but are almost always used by a single thread, so the locking they do is usually unnecessary. They support fine-grained methods, operating at the individual character or byte level, so not only do these classes tend to do unnecessary locking, but they tend to do a lot of it. This can result in significant performance loss. One paper reported a loss close to 20 percent in a real-world application [Heydon99]. You are unlikely to see performance losses this dramatic caused by unnecessary synchronization, but 5 to 10 percent is within the realm of possibility.

Arguably this belongs to the “small efficiencies” that Knuth says we should forget about (Item 37). If, however, you are writing a low-level abstraction that will generally be used by a single thread or as a component in a larger synchronized object, you should consider refraining from synchronizing the class internally. Whether or not you decide to synchronize a class, it is critical that you document its thread-safety properties (Item 52).

It is not always clear whether a given class should perform internal synchronization. In the nomenclature of Item 52, it is not always clear whether a class should be made thread-safe or thread-compatible. Here are a few guidelines to help you make this choice.

If you're writing a class that will be used heavily in circumstances requiring synchronization and also in circumstances where synchronization is not required, a reasonable approach is to provide both synchronized (thread-safe) and unsynchronized (thread-compatible) variants. One way to do this is to provide a wrapper class (Item 14) that implements an interface describing the class and performs appropriate synchronization before forwarding method invocations to the corresponding method of the wrapped object. This is the approach that was taken by the Collections Framework. Arguably, it should have been taken by java.util.Random as well. A second approach, suitable for classes that are not designed to be extended or reimplemented, is to provide an unsynchronized class and a subclass consisting solely of synchronized methods that invoke their counterparts in the superclass.

One good reason to synchronize a class internally is because it is intended for heavily concurrent use and you can achieve significantly higher concurrency by performing internal fine-grained synchronization. For example, it is possible to implement a nonresizable hash table that independently synchronizes access to each bucket. This affords much greater concurrency than locking the entire table to access a single entry.

If a class or a static method relies on a mutable static field, it must be synchronized internally, even if it is typically used by a single thread. Unlike a shared instance, it is not possible for the client to perform external synchronization because there can be no guarantee that other clients will do likewise. The static method Math.random exemplifies this situation.

In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, try to limit the amount of work that you do from within synchronized regions. When you are designing a mutable class, think about whether it should do its own synchronization. The cost savings that you can hope to achieve by dispensing with synchronization is no longer huge, but it is measurable. Base your decision on whether the primary use of the abstraction will be multithreaded, and document your decision clearly.

Item 50: Never invoke wait outside a loop

The Object.wait method is used to make a thread wait for some condition. It must be invoked inside a synchronized region that locks the object on which it is invoked. This is the standard idiom for using the wait method:

					synchronized (obj) {
    while (<condition does not hold>)
        obj.wait();

    ... // Perform action appropriate to condition
}

Always use the wait loop idiom to invoke the wait method. Never invoke it outside of a loop. The loop serves to test the condition before and after waiting.

Testing the condition before waiting and skipping the wait if the condition already holds are necessary to ensure liveness. If the condition already holds and notify (or notifyAll) method has already been invoked before a thread waits, there is no guarantee that the thread will ever waken from the wait.

Testing the condition after waiting and waiting again if the condition does not hold are necessary to ensure safety. If the thread proceeds with the action when the condition does not hold, it can destroy the invariants protected by the lock. There are several reasons a thread might wake up when the condition does not hold:

  • Another thread could have obtained the lock and changed the protected state between the time a thread invoked notify and the time the waiting thread woke up.

  • Another thread could have invoked notify accidentally or maliciously when the condition did not hold. Classes expose themselves to this sort of mischief by waiting on publicly accessible objects. Any wait contained in a synchronized method of a publicly accessible object is susceptible to this problem.

  • The notifying thread could be overly “generous” in waking waiting threads. For example, the notifying thread must invoke notifyAll even if only some of the waiting threads have their condition satisfied.

  • The waiting thread could wake up in the absence of a notify. This is known as a spurious wakeup. Although The Java Language Specification[JLS] does not mention this possibility, many JVM implementations use threading facilities in which spurious wakeups are known to occur, albeit rarely [Posix, 11.4.3.6.1].

A related issue is whether you should use notify or notifyAll to wake waiting threads. (Recall that notify wakes a single waiting thread, assuming such a thread exists, and notifyAll wakes all waiting threads.) It is often said that you should always use notifyAll. This is reasonable, conservative advice, assuming that all wait invocations are inside while loops. It will always yield correct results because it guarantees that you'll wake the threads that need to be awakened. You may wake some other threads too, but this won't affect the correctness of your program. These threads will check the condition for which they're waiting and, finding it false, will continue waiting.

As an optimization, you may choose to invoke notify instead of notifyAll if all threads that could be in the wait-set are waiting for the same condition and only one thread at a time can benefit from the condition becoming true. Both of these conditions are trivially satisfied if only a single thread waits on a particular object (as in the WorkQueue example, Item 49).

Even if these conditions appear true, there may be cause to use notifyAll in place of notify. Just as placing the wait invocation in a loop protects against accidental or malicious notifications on a publicly accessible object, using notifyAll in place of notify protects against accidental or malicious waits by an unrelated thread. Such waits could otherwise “swallow” a critical notification, leaving its intended recipient waiting indefinitely. The reason that notifyAll was not used in the WorkQueue example is that the worker thread waits on a private object (queue) so there is no danger of accidental or malicious waits.

There is one caveat concerning the advice to use notifyAll in preference to notify. While the use of notifyAll cannot harm correctness, it can harm performance. In fact, it systematically degrades the performance of certain data structures from linear in the number of waiting threads to quadratic. The class of data structures so affected are those for which only a certain number of threads are granted some special status at any given time and other threads must wait. Examples include semaphores, bounded buffers, and read-write locks.

If you are implementing this sort of data structure and you wake up each thread as it becomes eligible for “special status,” you wake each thread once for a total of n wakeups. If you wake all n threads when only one can obtain special status and the remaining n-1 threads go back to waiting, you will end up with n + (n – 1) + (n – 2) … + 1 wakeups by the time all waiting threads have been granted special status. The sum of this series is O(n2). If you know that the number of threads will always be small, this may not be a problem in practice, but if you have no such assurances, it is important to use a more selective wakeup strategy.

If all of the threads vying for special status are logically equivalent, then all you have to do is carefully use notify instead of notifyAll. If, however, only some of the waiting threads are eligible for special status at any given time, then you must use a pattern known as Specific Notification [Cargill96, Lea99]. This pattern is beyond the scope of this book.

In summary, always invoke wait from within a while loop, using the standard idiom. There is simply no reason to do otherwise. Usually, you should use notifyAll in preference to notify. There are, however, situations where doing so will impose a substantial performance penalty. If notify is used, great care must be taken to ensure liveness.

Item 51: Don't depend on the thread scheduler

When multiple threads are runnable, the thread scheduler determines which threads get to run and for how long. Any reasonable JVM implementation will attempt some sort of fairness when making this determination, but the exact policy varies greatly among implementations. Therefore well-written multithreaded programs should not depend on the details of this policy. Any program that relies on the thread scheduler for its correctness or performance is likely to be nonportable.

The best way to write a robust, responsive, portable multithreaded application is to ensure that there are few runnable threads at any given time. This leaves the thread scheduler with very little choice: It simply runs the runnable threads till they're no longer runnable. As a consequence, the program's behavior doesn't vary much even under radically different thread scheduling algorithms.

The main technique for keeping the number of runnable threads down is to have each thread do a small amount of work and then wait for some condition using Object.wait or for some time to elapse using Thread.sleep. Threads should not busy-wait, repeatedly checking a data structure waiting for something to happen. Besides making the program vulnerable to the vagaries of the scheduler, busy-waiting can greatly increase the load on the processor, reducing the amount of useful work that other processes can accomplish on the same machine.

The work queue example in Item 49 follows these recommendations: Assuming the client-provided processItem method is well behaved, the worker thread spends most of its time waiting on a monitor for the queue to become nonempty. As an extreme example of what not to do, consider this perverse reimplementation of WorkQueue, which busy-waits instead of using a monitor:

					// HORRIBLE PROGRAM - uses busy-wait instead of Object.wait!
public abstract class WorkQueue {
    private final List queue = new LinkedList();
    private boolean stopped = false;

    protected WorkQueue() { new WorkerThread().start(); }

    public final void enqueue(Object workItem) {
        synchronized (queue) { queue.add(workItem); }
    }
    public final void stop()  {
        synchronized (queue) { stopped = true; }
    }
    protected abstract void processItem(Object workItem)
        throws InterruptedException;
    private class WorkerThread extends Thread {
        public void run() {
            final Object QUEUE_IS_EMPTY = new Object();
            while (true) {  // Main loop
                Object workItem = QUEUE_IS_EMPTY;
                synchronized (queue) {
                    if (stopped)
                        return;
                    if (!queue.isEmpty())
                        workItem = queue.remove(0);
                }

                if (workItem != QUEUE_IS_EMPTY) {
                    try {
                        processItem(workItem);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }
        }
    }
}

To give you some idea of the price you'd pay for this sort of implementation, consider the following microbenchmark, which creates two work queues and passes a work item back and forth between them. (The work item passed from one queue to the other is a reference to the former queue, which serves as a sort of return address.) The program runs for ten seconds before starting measurement to allow the system to “warm up” and then counts the number of round trips from queue to queue in the next ten seconds. On my machine, the final version of WorkQueue in Item 49 exhibits 23,000 round trips per second, while the perverse implementation above exhibits 17 round trips per second:

class PingPongQueue extends WorkQueue {
    volatile int count = 0;

    protected void processItem(final Object sender) {
        count++;
        WorkQueue recipient = (WorkQueue) sender;
        recipient.enqueue(this);
    }
}
public class WaitQueuePerf {
    public static void main(String[] args) {
        PingPongQueue q1 = new PingPongQueue();
        PingPongQueue q2 = new PingPongQueue();
        q1.enqueue(q2); // Kick-start the system

        // Give the system 10 seconds to warm up
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }

        // Measure the number of round trips in 10 seconds
        int count = q1.count;
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }
        System.out.println(q1.count - count);

        q1.stop();
        q2.stop();
    }
}

While the WorkQueue implementation above may seem a bit farfetched, it's not uncommon to see multithreaded systems with one or more threads that are unnecessarily runnable. The results may not be as extreme as those demonstrated here, but performance and portability are likely to suffer.

When faced with a program that barely works because some threads aren't getting enough CPU time relative to others, resist the temptation to “fix” the program by putting in calls to Thread.yield. You may succeed in getting the program to work, but the resulting program will be nonportable from a performance standpoint. The same yield invocations that improve performance on one JVM implementation might make it worse on another and have no effect on a third. Thread.yield has no testable semantics. A better course of action is to restructure the application to reduce the number of concurrently runnable threads.

A related technique, to which similar caveats apply, is adjusting thread priorities. Thread priorities are among the least portable features of the Java platform. It is not unreasonable to tune the responsiveness of an application by tweaking a few thread priorities, but it is rarely necessary, and the results will vary from JVM implementation to JVM implementation. It is unreasonable to solve a serious liveness problem by adjusting thread priorities; the problem is likely to return until you find and fix the underlying cause.

The only use that most programmers will ever have for Thread.yield is to artificially increase the concurrency of a program during testing. This shakes out bugs by exploring a larger fraction of the program's state-space, thus increasing confidence in the correctness of the system. This technique has proven highly effective in ferreting out subtle concurrency bugs.

In summary, do not depend on the thread scheduler for the correctness of your application. The resulting application will be neither robust nor portable. As a corollary, do not rely on Thread.yield or thread priorities. These facilities are merely hints to the scheduler. They may be used sparingly to improve the quality of service of an already working implementation, but they should never be used to “fix” a program that barely works.

Item 52: Document thread safety

How a class behaves when its instances or static methods are subjected to concurrent use is an important part of the contract that the class establishes with its clients. If you do not document this component of a class's behavior, the programmers who use the class will be forced to make assumptions. If those assumptions are wrong, the resulting program may perform insufficient synchronization (Item 48) or excessive synchronization (Item 49). In either case, serious errors may result.

It is sometimes said that users can determine the thread safety of a method by looking for the presence of the synchronized modifier in the documentation generated by Javadoc. This is wrong on several counts. While the Javadoc utility did include the synchronized modifier in its output in releases prior to 1.2, this was a bug and has been fixed. The presence of the synchronized modifier in a method declaration is an implementation detail, not a part of the exported API. Its presence does not reliably indicate that a method is thread safe; it is subject to change from release to release.

Moreover, the claim that the presence of the synchronized keyword is sufficient to document thread safety embodies the common misconception that thread safety is an all-or-nothing property. In fact, there are many levels of thread safety that a class can support. To enable safe multithreaded use, a class must clearly document in prose the level of thread safety that it supports.

The following list summarizes the levels of thread safety that a class can support. This list is not meant to be exhaustive, but it covers the common cases. The names used in this list are not standard because there are no widely accepted conventions in this area:

  • immutable—. Instances of this class appear constant to their clients. No external synchronization is necessary. Examples include String, Integer, and BigInteger (Item 13).

  • thread-safe—. Instances of this class are mutable, but all methods contain sufficient internal synchronization that instances may be used concurrently without the need for external synchronization. Concurrent invocations will appear to execute serially in some globally consistent order. Examples include Random and java.util.Timer.

  • conditionally thread-safe—. Like thread-safe, except that the class (or an associated class) contains methods that must be invoked in sequence without interference from other threads. To eliminate the possibility of interference, the client must obtain an appropriate lock for the duration of the sequence. Examples include Hashtable and Vector, whose iterators require external synchronization.

  • thread-compatible—. Instances of this class can safely be used concurrently by surrounding each method invocation (and in some cases, each sequence of method invocations) by external synchronization. Examples include the general purpose collection implementations, such as ArrayList and HashMap.

  • thread-hostile—. This class is not safe for concurrent use by multiple threads, even if all method invocations are surrounded by external synchronization. Typically, thread hostility stems from the fact that methods modify static data that affect other threads. Luckily, there are very few thread-hostile classes or methods in the platform libraries. The System.runFinalizersOnExit method is thread-hostile, and has been deprecated.

Documenting a conditionally thread-safe class requires care. You must indicate which invocation sequences require external synchronization and which lock (or in rare cases, which locks) must be acquired to exclude concurrent access. Typically it is the lock on the instance itself, but there are exceptions. If an object represents an alternative view on some other object, the client must obtain a lock on the backing object so as to prevent direct modifications to the backing object. For example, the documentation for Hashtable.keys should say something like this:

If there is any danger of another thread modifying this hash table, safely enumerating over its keys requires that you lock the Hashtable instance prior to calling this method, and retain the lock until you are finished using the returned Enumeration, as demonstrated in the following code fragment:

Hashtable h = ...;

synchronized (h) {
    for (Enumeration e = h.keys(); e.hasMoreElements(); )
        f(e.nextElement());
}

As of release 1.3, Hashtable's documentation does not include this prose, but hopefully this situation will soon be remedied. More generally, the Java platform libraries could do a better job of documenting their thread safety.

While committing to the use of a publicly accessible lock object allows clients to perform a sequence of method invocations atomically, this flexibility comes at a price. A malicious client can mount a denial-of-service attack simply by holding the lock on the object:

					// Denial-of-service attack
synchronized (importantObject) {
    Thread.sleep(Integer.MAX_VALUE); // Disable importantObject
}

If you are concerned about this denial-of-service attack, you should use a private lock object to synchronize operations:

					// Private lock object idiom - thwarts denial-of-service attack
private Object lock = new Object();

public void  foo() {
    synchronized(lock) {
        ...
    }
}

Because the lock is obtained on an object that is inaccessible to clients, the containing object is immune from the denial-of-service attack shown above. Note that conditionally thread-safe classes are always prone to this attack because they must document the lock to be held when performing operation sequences atomically. Thread-safe classes, however, may be protected from this attack by the use of the private lock object idiom.

Using internal objects for locking is particularly suited to classes designed for inheritance (Item 15) such as the WorkQueue class in Item 49. If the superclass were to use its instances for locking, a subclass could unintentionally interfere with its operation. By using the same lock for different purposes, the superclass and the subclass could end up “stepping on each others' toes.”

To summarize, every class should clearly document its thread-safety properties. The only way to do this is to provide carefully worded prose descriptions. The synchronized modifier plays no part in documenting the thread safety of a class. It is, however, important for conditionally thread-safe classes to document which object must be locked to allow sequences of method invocations to execute atomically. The description of a class's thread safety generally belongs in the class's documentation comment, but methods with special thread-safety properties should describe these properties in their own documentation comments.

Item 53: Avoid thread groups

Along with threads, locks, and monitors, a basic abstraction offered by the threading system is thread groups. Thread groups were originally envisioned as a mechanism for isolating applets for security purposes. They never really fulfilled this promise, and their security importance has waned to the extent that they aren't even mentioned in the seminal work on the Java 2 platform security model [Gong99].

Given that thread groups don't provide any security functionality to speak of, what functionality do they provide? To a first approximation, they allow you to apply Thread primitives to a bunch of threads at once. Several of these primitives have been deprecated, and the remainder are infrequently used. On balance, thread groups don't provide much in the way of useful functionality.

In an ironic twist, the ThreadGroup API is weak from a thread safety standpoint. To get a list of the active threads in a thread group, you must invoke the enumerate method, which takes as a parameter an array large enough to hold all the active threads. The activeCount method returns the number of active threads in a thread group, but there is no guarantee that this count will still be accurate once an array has been allocated and passed to the enumerate method. If the array is too small, the enumerate method silently ignores any extra threads.

The API to get a list of the subgroups of a thread group is similarly flawed. While these problems could have been fixed with the addition of new methods, they haven't been fixed because there is no real need; thread groups are largely obsolete.

To summarize, thread groups don't provide much in the way of useful functionality, and much of the functionality they do provide is flawed. Thread groups are best viewed as an unsuccessful experiment, and you may simply ignore their existence. If you are designing a class that deals with logical groups of threads, just store the Thread references comprising each logical group in an array or collection. The alert reader may notice that this advice appears to contradict that of Item 30, “Know and use the libraries.” In this instance, Item 30 is wrong.

There is a minor exception to the advice that you should simply ignore thread groups. One small piece of functionality is available only in the ThreadGroup API. The ThreadGroup.uncaughtException method is automatically invoked when a thread in the group throws an uncaught exception. This method is used by the “execution environment” to respond appropriately to uncaught exceptions. The default implementation prints a stack trace to the standard error stream. You may occasionally wish to override this implementation, for example, to direct the stack trace to an application-specific log.

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

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