How a class
behaves when its instances or static methods are subjected to concurrent use is
an important part of the contract the class makes with its clients. If you
don’t document this facet of a class’s behavior, programmers who use the class
will be forced to make assumptions. If those assumptions are wrong, the
resulting program may perform insufficient synchronization (Item 66) or
excessive synchronization (Item 67). In either case, serious errors can result.
The presence of
the synchronized modifier in a
method declaration is an implementation detail, not a part of its exported API.
It
does not reliably indicate that a method is thread-safe.
To enable safe
concurrent use, a class must clearly document what level of thread safety it
supports.
The following
list summarizes levels of thread safety. It is not exhaustive but covers the
common cases:
• immutable—Instances of
this class appear constant. No external synchronization is necessary. Examples
include String, Long, and BigInteger
(Item
15).
• unconditionally
thread-safe—Instances of this class are mutable, but the class has sufficient
internal synchronization that its instances can be used concurrently without
the need for any external synchronization. Examples include Random and ConcurrentHashMap.
• conditionally
thread-safe—Like unconditionally thread-safe, except that some methods require
external synchronization for safe concurrent use. Examples include the
collections returned by the Collections.synchronized
wrappers,
whose iterators require external synchronization.
• not thread-safe—Instances of
this class are mutable. To use them concurrently, clients must surround each
method invocation (or invocation sequence) with external synchronization of the
clients’ choosing. Examples include the general-purpose collection
implementations, such as ArrayList and HashMap.
• thread-hostile—This class is
not safe for concurrent use even if all method invocations are surrounded by
external synchronization. Thread hostility usually results from modifying
static data without synchronization. No one writes a thread-hostile class on
purpose; such classes result from the failure to consider concurrency. Luckily,
there are very few thread-hostile classes or methods in the Java libraries. The
System.runFinalizersOnExit method is
thread-hostile and has been deprecated.
These
categories (apart from thread-hostile) correspond roughly to the thread
safety annotations in Java Concurrency in Practice, which are Immutable, ThreadSafe, and NotThreadSafe.
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 execute these sequences. Typically
it is the lock on the instance itself, but there are exceptions. If an object
represents a view on some other object, the client generally must synchronize on the
backing object, so as to prevent its direct modification. For example, the
documentation for Collections.synchronizedMap says this:
It is
imperative that the user manually synchronize on the returned map when iterating
over any of its collection views:
Map<K, V> m = Collections.synchronizedMap(new
HashMap<K, V>());
...
Set<K> s = m.keySet(); // Needn't be in
synchronized block
...
synchronized(m) { // Synchronizing on m, not s!
for (K key : s)
key.f();
}
Failure to
follow this advice may result in non-deterministic behavior.
To prevent
this denial-of-service attack, you can use a private lock object instead of
using synchronized methods (which imply a publicly accessible lock):
// Private lock object idiom - thwarts
denial-of-service attack
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
...
}
}
Because the
private lock object is inaccessible to clients of the class, it is impossible
for them to interfere with the object’s synchronization.
To summarize,
every class should clearly document its thread safety properties with a
carefully worded prose description or a thread safety annotation. The synchronized modifier plays no part in this documentation.
Conditionally thread-safe classes must document which method invocation
sequences require external synchronization, and which lock to acquire when
executing these sequences. If you write an unconditionally thread-safe class,
consider using a private lock object in place of synchronized methods. This
protects you against synchronization interference by clients and subclasses and
gives you the flexibility to adopt a more sophisticated approach to concurrency
control in a later release.
Reference: Effective Java 2nd Edition by Joshua Bloch