57. Locks and Conditions
ReentrantLock offers more flexibility than synchronized โ timed and interruptible lock attempts, fairness, and Condition variables; ReadWriteLock allows concurrent reads; StampedLock adds optimistic reading.
Explanation
ReentrantLock must always be unlocked in a finally block to guarantee release even if an exception occurs. Unlike synchronized, it supports tryLock() (non-blocking attempt), tryLock(time, unit) (timed attempt), lockInterruptibly() (responds to interrupt), and a fairness option (new ReentrantLock(true) โ threads acquire in FIFO order, preventing starvation but reducing throughput).
ReentrantReadWriteLock gives separate read and write locks. Multiple threads can hold the read lock simultaneously; the write lock is exclusive. Good for data structures read far more often than written.
StampedLock (Java 8+) adds optimistic reading: try to read without acquiring any lock (tryOptimisticRead() returns a stamp), read data, then validate the stamp. If validation fails (a write occurred), fall back to a pessimistic read lock. This gives maximum read throughput when writes are rare.
Example
import java.util.concurrent.locks.*;
public class LocksAndConditions {
// Bounded buffer using ReentrantLock + Conditions
static class BoundedBuffer<T> {
private final Object[] items;
private int head, tail, count;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
BoundedBuffer(int capacity) { items = new Object[capacity]; }
@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) notEmpty.await();
T item = (T) items[head];
items[head] = null;
head = (head + 1) % items.length;
count--;
notFull.signal();
return item;
} finally { lock.unlock(); }
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) notFull.await();
items[tail] = item;
tail = (tail + 1) % items.length;
count++;
notEmpty.signal();
} finally { lock.unlock(); }
}
}
public static void main(String[] args) throws InterruptedException {
BoundedBuffer<Integer> buf = new BoundedBuffer<>(3);
Thread prod = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try { buf.put(i); System.out.println("Put: " + i); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
Thread cons = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try { System.out.println("Got: " + buf.take()); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
prod.start(); cons.start();
prod.join(); cons.join();
}
}Sources
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html โ ReentrantLock API
- https://docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html โ Lock objects tutorial