Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Effective Java Programming Language Guide - Bloch J..pdf
Скачиваний:
41
Добавлен:
24.05.2014
Размер:
2.93 Mб
Скачать

Effective Java: Programming Language Guide

may be used sparingly to improve the quality implementation, but they should never be used to “fix”

of service of an already working 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.

154

Effective Java: Programming Language Guide

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

155

Effective Java: Programming Language Guide

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

156

Effective Java: Programming Language Guide

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.

157