Synchronization and Visibility

Learning goals

By the end of this chapter you should be able to:

  • Explain race conditions and why count++ is not atomic
  • Use synchronized, ReentrantLock, and volatile for the right problems
  • Apply java.util.concurrent utilities: CountDownLatch, Semaphore, ConcurrentHashMap
  • State the happens-before rules at a high level

Shared mutable state

Threads share the heap. Without coordination, two threads updating the same field interleave unpredictably.

class Counter {
    int count = 0;
    void inc() { count++; }  // read-modify-write — NOT atomic
}

count++ compiles to load, add, store — another thread can interleave between steps.


synchronized

Every object has an intrinsic lock. synchronized ensures one thread at a time in a block or method.

class Counter {
    private int count = 0;

    synchronized void inc() {
        count++;
    }

    synchronized int get() {
        return count;
    }
}

synchronized method locks this. static synchronized locks the Class object.

Prefer private final lock object when you need finer control:

private final Object lock = new Object();

void update() {
    synchronized (lock) {
        // critical section
    }
}

ReentrantLock

Explicit lock with tryLock, fairness option, Condition support:

private final ReentrantLock lock = new ReentrantLock();

void update() {
    lock.lock();
    try {
        // critical section
    } finally {
        lock.unlock();  // always in finally
    }
}

Use when you need tryLock with timeout, interruptible lock acquisition, or multiple Condition queues.


volatile and visibility

volatile guarantees visibility (reads see latest write) and ordering for the field — not compound atomicity.

volatile boolean shutdown = false;

void run() {
    while (!shutdown) {
        doWork();
    }
}

Good for single-writer flags. volatile counter increment is still wrong — use AtomicInteger or synchronization.


Atomic classes

AtomicInteger counter = new AtomicInteger();

counter.incrementAndGet();
counter.compareAndSet(expect, update);

LongAdder under high contention — stripes updates, better throughput than hot AtomicLong.


Concurrent collections (preview)

Collections.synchronizedList wraps every method with one lock — simple but contended.

ConcurrentHashMap — segmented/CAS updates for scalable maps.

CopyOnWriteArrayList — snapshot on write; excellent read-heavy, rare-write lists.

Full chapter on concurrent collections follows — use the right structure instead of synchronizing every access to HashMap.


Coordination utilities

CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(workerCount);

// workers await start, count down done; main awaits done
Semaphore permits = new Semaphore(10);
permits.acquire();
try {
    accessLimitedResource();
} finally {
    permits.release();
}

happens-before (essentials)

If action A happens-before B, B sees A’s effects. Key rules:

  • unlock happens-before subsequent lock on same monitor
  • volatile write happens-before subsequent read of that volatile
  • Thread.start happens-before any action in started thread
  • ConcurrentHashMap operations have safe publication semantics

You do not memorize every edge — know that unsynchronized reads/writes have no guarantee.


Common mistakes

  1. Synchronizing only writes — readers also need sync or volatile/atomic.
  2. Locking on interned strings or boxed Integers — accidental shared locks.
  3. Holding lock during IO — blocks other threads; shrink critical section.
  4. Double-checked locking without volatile — broken pre-Java 5 memory model; use holder idiom or enum singleton.
  5. Assuming ConcurrentHashMap makes whole app thread-safe — compound actions still need coordination.

Practice checkpoint

  1. Flag to stop worker threads — volatile boolean enough? Answer: yes for single boolean flag written once from main, read in loop.

  2. Increment shared counter 1000 times from 10 threads — minimal fix? Answer: AtomicInteger, or synchronized inc(), or LongAdder.

  3. What does ReentrantLock offer over synchronized? Answer: tryLock/timeouts, fairness, multiple Conditions, interruptible lock.

  4. Two threads put/get same HashMap — safe? Answer: no — use ConcurrentHashMap or external synchronization.


Interview angles

  • Race vs deadlock vs livelock — define each; deadlock needs circular wait.
  • Visibility vs atomicity — volatile covers former, not latter for compound ops.
  • java.util.concurrent vs synchronized — prefer j.u.c for scalable primitives and collections.
  • Immutable objects — simplest thread safety; share freely after construction.

Next: /java/learn/concurrency/concurrent-collections/