In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions “must” happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect.
Always use the proper synchronization whenever data is shared across threads.
Stale data can cause serious and confusing failures such as unexpected exceptions, corrupted data structures, inaccurate computations, and infinite loops.
Even if you don’t care about stale values, it is not safe to use shared mutable long
and double
variables in
multithreaded programs unless they are declared volatile
or guarded by a lock.
Locking is not just about mutual exclusion; it is also about memory visibility. To ensure that all threads see the most up-to-date values of shared mutable variables, the reading and writing threads must synchronize on a common lock.
When a field is declared volatile
, the compiler and runtime are put on notice that this variable is shared and that
operations on it should not be reordered with other memory operations. Volatile variables are not cached in registers
or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most
recent write by any thread.
Use volatile
variables only when they simplify implementing and verifying your synchronization policy; avoid
using volatile variables when veryfing correctness would require subtle reasoning about visibility. Good uses of
volatile
variables include ensuring the visibility of their own state, that of the object they refer to, or
indicating that an important lifecycle event (such as initialization or shutdown) has occurred.
Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.
You can use volatile variables only when all the following criteria are met:
- Writes to the variable do not depend on its current value, or you can ensure hat only a single thread ever updates the value;
- The variable does not participate in invariants with other state variables; and
- Locking is not required for any other reason while the variable is being accessed.
Publishing an object means making it available to code outside of its current scope, such as by storing a reference to it where other code can find it, returning it from a nonprivate method, or passing it to a method in another class.
An object that is published when it should not have been is said to have escaped.
Publishing one object may indirectly publish others.
Any object that is reachable from a published object by following some chain of nonprivate field references and method calls has also been published.
From the perspective of a class C
, an alien method is one whose behavior is not fully specified by C
. This
includes methods in other classes as well as overrideable methods (neither private
nor final
) in C
itself.
Passing an object to an alien method must also be considered publishing that object. Since you can’t know what code
will actually be invoked, you don’t know that the alien method won’t publish the object or retain a reference to it
that might later be used from another thread.
An object or its internal state is also published when an inner class instance is published, because the inner class instances contain a hidden reference to the enclosing instance. i.e.:
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
Do not allow the this
reference to escape during construction. i.e.:
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
If data is only accessed from a single thread, no synchronization is needed. This technique, thread confinement, is one of the simplest ways to achieve thread safety.
Thread confinement is an element of your program’s design that must be enforced by its implementation. The language and
core libraries provide mechanisms that can help in maintaining thread confinement—local variables and the ThreadLocal
class—but even with these, it is still the programmer’s responsibility to ensure that thread-confined objects do not
escape from their intended thread.
Ad-hoc thread confinement describes when the responsibility for maintaining thread confinement falls entirely on the implementation.
Because of its fragility, ad-hoc thread confinement should be used sparingly; if possible, use one of the stronger
forms of thread confinment (stack confinement or ThreadLocal
) instead.
Stack confinement is a special case of thread confinement in which an object can only be reached through local variables.
ThreadLocal allows you to associate a per-thread value with a value-holding object and provides get
and set
accessor methods that maintain a separate copy of the value for each thread that uses it, so a get
returns the most
recent value passed to set
from the currently executing thread.
Like global variables, thread-local variables can detract from reusability and introduce hidden couplings among classes, and should therefore be used with care.
Immutable objects are always thread-safe.
An object is immutable if:
- Its state cannot be modified after construction
- All its fields are final
- It is properly constructed (the
this
reference does not escape during construction)
Just as it is a good practice to make all fields private
unless they need greater visibility, it is a good practice
to make all fields final
unless they need to be mutable.
Whenever a group of related data items must be acted on atomically, consider creating an immutable holder class for them. Race conditions in accessing or updating multiple related variables can be eliminated by using an immutable object to hold all the variables.
When synchronization is not used to make an object instance visible to other threads, we say the object was not properly published.
Because immutable objects are so important, the Java Memory Model offers a special guarantee of initialization safety for sharing immutable objects.
Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.
To publish an object safely, both the reference to the object and the object’s state must be made visible to other threads at the same time. A properly constructed object can be safely published by:
- Initializing an object reference from a static initializer;
- Storing a reference to it into a volatile field or AtomicReference;
- Storing a reference to it into a final field of a properly constructed object; or
- Storing a reference to it into a field that is properly guarded by a lock
The thread-safe library collections offer the following safe publication guarantees:
- Placing a key or value in a
Hashtable
,SynchronizedMap
, orConcurrentMap
safely publishes it to any thread that retrieves it from theMap
(whether directly or via an iterator); - Placing an element in a
Vector
,CopyOnWriteArrayList
,CopyOnWriteArraySet
,SynchronizedList
, orSynchronizedSet
safely publishes it to any thread that retrieves it from the collection; - Placing an element on a
BlockingQueue
or aConcurrentLinkedQueue
safely publishes it to any thread that retrieves it from the queue.
Objects that are not technically immutable, but whose state will not be modified after publication, are called effectively immutable.
Safely published effectively immutable objects can be used safely by any thread without additional synchronization.
To share mutable objects safely, they must be safely published and be either thread-safe or guarded by a lock.
The publication requirements for an object depend on its mutability:
- Immutable objects can be published through any mechanism;
- Effectively immutable objects must be safely published;
- Mutable objects must be safely published, and must be either thread-safe or guarded by a lock.
The most useful policies for using and sharing objects in a concurrent program are:
-
Thread-confined. A thread-confined object is owned exclusively by and confined to one thread, and can be modified by its owning thread.
-
Shared read-only. A shared read-only object can be accessed concurrently by multiple threads without additional synchronization, but cannot be modified by any thread. Shared read-only objects include immutable and effectively immutable objects.
-
Shared thread-safe. A thread-safe object performs synchronization internally, so multiple threads can freely access it through its public interface without further synchronization.
-
Guarded. A guarded object can be accessed only with a specific lock held. Guarded objects include those that are encapsulated within other thread-safe objects and published objects that are known to be guarded by a specific lock.