Synchronization & Intrinsic Locks — Java Interview Guide | Cracked Java
Mid

Synchronization & Intrinsic Locks

Race conditions, the synchronized keyword and the monitor it locks, reentrancy, and the wait/notify guarded-block protocol.

Prereqs: thread-fundamentals

When two threads touch the same mutable state and at least one writes, you have a data race — and without coordination the result is undefined. synchronized is Java's built-in answer: every object carries a hidden monitor (an intrinsic lock), and synchronized lets exactly one thread hold that monitor at a time. Holding it does two jobs at once: it gives mutual exclusion and it establishes the happens-before edges that make one thread's writes visible to the next.

The monitor

Every Java object has an associated monitor, implemented in the object header (the mark word) and managed by the JVM. A synchronized region compiles to a monitorenter / monitorexit bytecode pair. A thread that reaches monitorenter either acquires the monitor or blocks (state BLOCKED) until the current owner releases it. The lock is reentrant — the owning thread can re-enter the same monitor without deadlocking, tracked by a recursion counter.

Thread-A --monitorenter--> [ owns lock ]  count=1
Thread-B --monitorenter--> BLOCKED (waits in entry set)
Thread-A --monitorexit---> count=0, lock free
Thread-B --acquires------> [ owns lock ]
One monitor, one owner at a time

What gets locked

synchronized always locks an object, never a block of code. An instance synchronized method locks this; a static synchronized method locks the Class object; a synchronized (obj) block locks whatever reference you name. Two threads only contend if they synchronize on the same object — a frequent source of "I added synchronized and it still races" bugs.

Coordination: wait / notify

Beyond mutual exclusion, a monitor has a wait set. A thread that owns the lock can call wait() to release it and park until another thread calls notify()/notifyAll() on the same monitor. This is the guarded block pattern: wait while a condition is false, act once it becomes true.

These primitives are low-level and easy to misuse. The java.util.concurrent toolkit (explicit locks, atomics, concurrent collections) is built on the same memory-model guarantees but is safer for everyday code — yet interviewers still probe synchronized because it reveals whether you understand why the higher-level tools exist.

Questions

7 in this topic