Concurrencycritical

synchronized vs ReentrantLock

synchronized is the built-in Java locking mechanism — it can guard a method or a code block and is automatically released when the block exits (including exceptions). ReentrantLock is an explicit lock from java.util.concurrent.locks that must be manually unlocked.

Memory anchor

synchronized = automatic bathroom door lock (unlocks when you leave, even if you faint). ReentrantLock = a padlock you carry -- more features (timed, interruptible), but forget to unlock and everyone waits forever.

Expected depth

Both provide mutual exclusion and visibility guarantees. ReentrantLock extras: tryLock() (non-blocking attempt), tryLock(timeout) (timed attempt), lockInterruptibly() (can be interrupted while waiting), multiple Condition objects (finer-grained wait/notify than the single wait set of Object monitors), and fairness policy (FIFO ordering, at a throughput cost). synchronized is easier to use correctly (automatic release); ReentrantLock is more flexible but requires try/finally to guarantee unlock. Reentrancy: a thread holding the lock can re-acquire it without deadlocking — both support this.

Deep — senior internals

HotSpot JVM implements synchronized with biased locking (disabled by default Java 15, removed Java 18), lightweight locking (CAS-based thin lock), and heavyweight (inflated) monitor. Lock inflation follows contention. JEP 374 disabled biased locking by default in Java 15 because modern concurrent workloads showed revocation overhead outweighing the benefit. Virtual threads (Project Loom, Java 21) cannot park on synchronized monitors without pinning the carrier thread (limits scalability) — ReentrantLock with LockSupport is preferred for Loom-friendly code. StampedLock adds optimistic reads for read-heavy workloads at the cost of not being reentrant.

🎤Interview-ready answer

synchronized is simpler and automatically releases on block exit (including exceptions), but offers only one wait set and no timeout. ReentrantLock gives you tryLock(), timed and interruptible acquisition, fairness options, and multiple Condition queues — necessary for producer-consumer with separate 'not full' and 'not empty' conditions. Always unlock ReentrantLock in a finally block. With Java 21 virtual threads, prefer ReentrantLock over synchronized to avoid pinning the carrier thread.

Common trap

Forgetting to unlock a ReentrantLock in an exception path (by not using try/finally) leaves the lock permanently held and deadlocks all threads waiting for it — synchronized never has this problem.