Concurrency

Thread Safety

A class is thread-safe when it behaves correctly under concurrent access with no extra coordination by callers. The core discipline (JCiP): identify shared mutable state, and guard every access to it with the same lock — or remove the sharing, or the mutability.
  • The problem is exactly shared + mutable state — remove either property and safety is free
  • Race conditions: check-then-act and read-modify-write compound actions are not atomic
  • Every shared mutable variable needs one guarding policy, applied on every access — reads too
  • Stateless objects and immutable objects are always thread-safe
  • Document the policy (@GuardedBy("lock") thinking) — safety is a design property, not a code style
The canonical race (JCiP Listing 2.2)
public class UnsafeCounter {
    private long count = 0;
    public void increment() { count++; }     // read-modify-write: THREE operations
}
// Two threads calling increment() 10,000 times each
// routinely produce a total under 20,000 — lost updates.

count++ is load, add, store. Two threads interleave: both load 9, both store 10 — one increment vanishes. Check-then-act is the same disease: if (!map.containsKey(k)) map.put(k, v) can both pass the check. Compound actions must execute atomically relative to other accesses of the same state.

Three cures
// 1. Synchronize every access (one lock, all accesses):
private long count;
public synchronized void increment() { count++; }
public synchronized long get() { return count; }

// 2. Delegate to an atomic ([[atomics-nonblocking]]):
private final AtomicLong count = new AtomicLong();
public void increment() { count.incrementAndGet(); }

// 3. Don't share: confine to one thread, or make the state immutable.

JCiP's design hierarchy: don't share (thread confinement — locals, ThreadLocal, per-request objects), don't mutate (immutable objects), or synchronize consistently. Guarding some accesses is worthless — a single unsynchronized read can observe garbage (Java Memory Model). And atomicity must cover the whole invariant: two AtomicLongs that must change together are not atomic together.

Sources
  • Java Concurrency in PracticeCh. 1–2 — Introduction; Thread Safety
  • Effective Java (3rd ed.)Item 78
  • Core Java, Volume I: Fundamentals (13th ed.)Ch. 10.5 — Synchronization