ReentrantLock

ReentrantLock provides explicit mutual exclusion with capabilities beyond intrinsic locking, including interruptible and timed acquisition. It is useful when a Java program needs more control over how threads acquire and release a lock.

ReentrantLock is an implementation of the `Lock` interface in `java.util.concurrent.locks`. Like `synchronized`, it provides mutual exclusion: while one thread holds the lock, another thread cannot enter a protected critical section through that same lock.

The lock is reentrant, meaning the thread that already owns it can acquire it again without blocking itself. Each successful `lock()` acquisition must eventually be matched by an `unlock()` by the owning thread, so the usual pattern is to acquire the lock and release it in a `finally` block.

Unlike intrinsic locking with `synchronized`, ReentrantLock provides operations such as `lockInterruptibly()`, timed `tryLock(...)`, and non-blocking `tryLock()`. It can also be constructed with a fairness policy, although fairness can affect throughput and does not guarantee that every scheduling outcome will be perfectly ordered.

A ReentrantLock is appropriate when explicit lock management or these additional acquisition modes are useful. For straightforward mutual exclusion, `synchronized` is often simpler and automatically releases the monitor when control leaves the synchronized region.

Key points

Interview questions

What is ReentrantLock, and why would you use it instead of synchronized?

ReentrantLock is an explicit lock implementation from `java.util.concurrent.locks`. It provides mutual exclusion like `synchronized`, but the programmer explicitly calls `lock()` and `unlock()`. It also supports features such as `tryLock()`, timed acquisition, and interruptible acquisition. I would use it when those capabilities or explicit lock management are needed; otherwise, `synchronized` is often simpler.

What does reentrant mean in ReentrantLock?

Reentrant means that a thread holding the lock can acquire that same lock again without blocking itself. ReentrantLock keeps track of the owning thread and an internal hold count. Each successful acquisition increases that count, and each corresponding `unlock()` decreases it. The lock becomes available to another thread only after the hold count reaches zero.

Why should ReentrantLock be unlocked in a finally block?

An exception can occur while the critical section is executing. If `unlock()` is only placed after that code, the exception may prevent the release from happening. Putting `unlock()` in `finally` ensures the release is attempted whenever the corresponding acquisition succeeded. The standard pattern is to call `lock()` before entering the `try` block and call `unlock()` in `finally`.

What is the difference between lock(), tryLock(), and lockInterruptibly()?

`lock()` waits until the lock can be acquired and does not provide a cancellation mechanism through thread interruption during that acquisition. `tryLock()` can return immediately with a boolean indicating whether acquisition succeeded, and its timed form can wait for a specified duration. `lockInterruptibly()` waits for acquisition but allows the waiting thread to respond to interruption by throwing `InterruptedException`. These choices let code express different waiting and cancellation requirements.

What does the fairness option of ReentrantLock do?

A ReentrantLock can be constructed with a fairness setting that influences which waiting threads are selected when the lock becomes available. A fair lock generally favors the longest-waiting eligible thread, while a non-fair lock permits more opportunistic acquisition. Fairness can reduce some starvation scenarios but may reduce throughput compared with non-fair acquisition. It also does not turn thread scheduling into a strict FIFO system.