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, andvolatilefor the right problems - Apply
java.util.concurrentutilities: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:
unlockhappens-before subsequentlockon same monitorvolatilewrite happens-before subsequent read of that volatileThread.starthappens-before any action in started threadConcurrentHashMapoperations have safe publication semantics
You do not memorize every edge — know that unsynchronized reads/writes have no guarantee.
Common mistakes
- Synchronizing only writes — readers also need sync or
volatile/atomic. - Locking on interned strings or boxed Integers — accidental shared locks.
- Holding lock during IO — blocks other threads; shrink critical section.
- Double-checked locking without volatile — broken pre-Java 5 memory model; use holder idiom or enum singleton.
- Assuming
ConcurrentHashMapmakes whole app thread-safe — compound actions still need coordination.
Practice checkpoint
-
Flag to stop worker threads —
volatile booleanenough? Answer: yes for single boolean flag written once from main, read in loop. -
Increment shared counter 1000 times from 10 threads — minimal fix? Answer:
AtomicInteger, orsynchronized inc(), orLongAdder. -
What does
ReentrantLockoffer oversynchronized? Answer: tryLock/timeouts, fairness, multiple Conditions, interruptible lock. -
Two threads put/get same
HashMap— safe? Answer: no — useConcurrentHashMapor 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.