Concurrency
Concurrency Doctrine (EJ 78–84 + JCiP)
The distilled rules: synchronize all access or none, keep locks small and private, prefer executors and utilities to threads and wait/notify, document thread safety honestly, and treat lazy initialization as a last resort.
- Synchronize access to shared mutable data — reads included (78)
- Avoid excessive synchronization: no alien calls under a lock (79)
- Executors/tasks over threads (80); utilities over wait/notify (81)
- Document thread safety: immutable / unconditionally safe / conditionally / not / hostile (82)
- Lazy initialization judiciously: holder idiom for statics, volatile DCL for instances (83)
- Never depend on the thread scheduler, priorities, or
Thread.yield(84)
| Level | Meaning | Examples |
|---|---|---|
| Immutable | constant; no external synchronization ever | String, Long, BigInteger, records of immutables |
| Unconditionally thread-safe | internally synchronized, use freely | AtomicLong, ConcurrentHashMap |
| Conditionally thread-safe | some sequences need client locking | Collections.synchronizedMap iteration |
| Not thread-safe | callers must synchronize every access | ArrayList, HashMap |
| Thread-hostile | unsafe even fully synchronized (static mutable abuse) | rare; usually a bug |
Documentation is part of the API: callers can't see your synchronization policy from signatures. State the level; for conditional safety, state exactly which sequences need which lock (the synchronizedMap javadoc pattern: iterate while holding the map). Undocumented assumptions become other people's data races.
// Almost always best: don't be lazy.
private final FieldType field = computeFieldValue();
// Lazy static — the holder idiom (JVM does the synchronization):
private static class Holder { static final FieldType FIELD = computeFieldValue(); }
static FieldType getField() { return Holder.FIELD; }
// Lazy instance — volatile double-check:
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) {
synchronized (this) {
if (field == null) field = result = computeFieldValue();
}
}
return result;
}JCiP's closing wisdom compresses further: less mutable state (every removed field is removed analysis), less sharing (confinement is free safety), more immutability, and when synchronization is unavoidable, make the policy boring — one obvious lock per invariant, documented, held briefly. Clever concurrency is a maintenance liability; simple concurrency survives refactoring.