ReentrantLock and Locks
Advancedjava.util.concurrent.locks provides explicit locking with more flexibility than synchronized — tryLock, timed locking, multiple conditions, and fairness.
Overview
ReentrantLock from java.util.concurrent.locks provides all the capabilities of synchronized plus more: tryLock() to avoid deadlocks, timed lock acquisition, multiple Condition objects for fine-grained wait/notify, and optional fairness. ReadWriteLock allows concurrent reads while serializing writes. StampedLock (Java 8) adds optimistic reading. Explicit locks must always be unlocked in a finally block.
ReentrantLock Basics
ReentrantLock is reentrant — the same thread can acquire the lock multiple times without deadlocking (just as synchronized is reentrant). It must be unlocked the same number of times it was locked.
Always call unlock() in a finally block to guarantee release even if an exception is thrown.
import java.util.concurrent.locks.*;
public class SafeCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // acquire
try {
count++;
} finally {
lock.unlock(); // ALWAYS release in finally
}
}
public int get() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
// tryLock — non-blocking attempt
public boolean tryIncrement() {
if (lock.tryLock()) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false; // lock not available
}
}Condition Variables
ReentrantLock supports multiple Condition objects — each is like a separate wait-set. This is more powerful than the single wait-set of synchronized (Object.wait/notify).
Use case: a BoundedBuffer with separate "not full" and "not empty" conditions. notFull.signal() only wakes producers; notEmpty.signal() only wakes consumers — more efficient than notifyAll().
public class BoundedBuffer<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBuffer(int capacity) { this.capacity = capacity; }
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity)
notFull.await(); // wait on "not full"
queue.add(item);
notEmpty.signal(); // wake one consumer
} finally { lock.unlock(); }
}
public T take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty())
notEmpty.await(); // wait on "not empty"
T item = queue.poll();
notFull.signal(); // wake one producer
return item;
} finally { lock.unlock(); }
}
}ReadWriteLock and StampedLock
ReadWriteLock allows multiple concurrent readers but exclusive writers. This is ideal for read-heavy caches.
StampedLock (Java 8) adds optimistic reading: read without locking, then validate the stamp — if valid, no lock was needed; if not, upgrade to a full read lock. Much faster for read-heavy workloads.
import java.util.concurrent.locks.*;
// ReadWriteLock — concurrent reads, exclusive write
public class Cache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public V get(K key) {
r.lock(); // shared read lock
try { return map.get(key); }
finally { r.unlock(); }
}
public void put(K key, V value) {
w.lock(); // exclusive write lock
try { map.put(key, value); }
finally { w.unlock(); }
}
}
// StampedLock — optimistic read
StampedLock sl = new StampedLock();
double x, y;
long stamp = sl.tryOptimisticRead(); // no lock acquired
x = this.x; y = this.y; // read values
if (!sl.validate(stamp)) { // check if write occurred
stamp = sl.readLock(); // upgrade to read lock
try { x = this.x; y = this.y; }
finally { sl.unlockRead(stamp); }
}Interactive Visualization
Key Points to Remember
- Always unlock in a finally block — otherwise a lock is permanently held on exception.
- tryLock() avoids deadlocks by allowing a thread to back off if the lock is unavailable.
- Multiple Condition objects allow fine-grained wait/signal (unlike synchronized's single wait-set).
- ReadWriteLock: multiple concurrent readers, exclusive writer — great for read-heavy caches.
- StampedLock optimistic reading avoids locking when no concurrent write occurred.
Practice ReentrantLock and Locks in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat advantages does ReentrantLock have over synchronized?
Why must unlock() be called in a finally block?
How does ReadWriteLock improve throughput for read-heavy workloads?
What is the difference between Condition.await() and Object.wait()?
What is optimistic locking in StampedLock?
Ask Aria about ReentrantLock and Locks
Your personal AI tutor — ask anything about this concept