ReentrantLock
ReentrantLock is the primary Lock implementation in java.util.concurrent.locks and the direct replacement for synchronized when more control over locking is needed. Like Java's intrinsic locks, it is reentrant — the same thread can acquire it multiple times without deadlocking, and must release it the same number of times to fully unlock. Unlike intrinsic locks, ReentrantLock exposes its reentrancy count, supports timed and interruptible lock acquisition, allows inspection of lock state and waiting threads, and can be constructed in fair mode (FIFO ordering) or the default unfair mode (higher throughput). ReentrantLock is backed by AbstractQueuedSynchronizer (AQS), which maintains a CLH queue of waiting threads and uses LockSupport.park/unpark for blocking and waking. This entry covers construction and fair vs unfair mode in depth, all acquisition methods with timing and interruption semantics, reentrancy mechanics and hold count, lock inspection methods, performance characteristics, and the canonical patterns for safe ReentrantLock usage in real concurrent systems.
Construction, Fairness, and Acquisition Methods
// ── Construction: fair vs unfair ─────────────────────────────────────
ReentrantLock unfairLock = new ReentrantLock(); // default: unfair (higher throughput)
ReentrantLock fairLock = new ReentrantLock(true); // fair: FIFO, prevents starvation
// ── lock() — blocks until acquired, ignores interruption ──────────────
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
performCriticalWork();
} finally {
lock.unlock(); // ALWAYS in finally
}
// ── lockInterruptibly() — cancellable lock wait ────────────────────────
public void cancellableOperation(ReentrantLock lock) throws InterruptedException {
lock.lockInterruptibly(); // throws InterruptedException if thread is interrupted while waiting
try {
performCriticalWork();
} finally {
lock.unlock();
}
}
Thread worker = new Thread(() -> {
try {
cancellableOperation(lock);
System.out.println("Work completed");
} catch (InterruptedException e) {
System.out.println("Operation cancelled — was waiting for lock");
Thread.currentThread().interrupt();
}
});
lock.lock(); // hold the lock so worker must wait
worker.start();
Thread.sleep(200);
worker.interrupt(); // cancels worker's wait on lockInterruptibly()
Thread.sleep(100);
lock.unlock();
// ── tryLock() — non-blocking ──────────────────────────────────────────
if (lock.tryLock()) {
try {
System.out.println("Got lock immediately");
} finally {
lock.unlock();
}
} else {
System.out.println("Lock busy — skip or retry");
}
// ── tryLock(timeout) — give up after waiting ──────────────────────────
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("Got lock within 1 second");
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock in 1 second — aborting");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// ── Deadlock breaking with tryLock ────────────────────────────────────
public static boolean transferSafe(ReentrantLock lockA, ReentrantLock lockB, Runnable work)
throws InterruptedException {
while (true) {
if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
work.run();
return true;
} finally { lockB.unlock(); }
}
// lockB not acquired — release lockA and retry after backoff
} finally { lockA.unlock(); }
}
Thread.sleep(1 + ThreadLocalRandom.current().nextLong(10)); // random backoff
}
}Reentrancy, Hold Count, and Lock Inspection
// ── Reentrancy: same thread can lock multiple times ──────────────────
ReentrantLock lock = new ReentrantLock();
lock.lock(); // hold count: 1
try {
System.out.println("Outer: " + lock.getHoldCount()); // 1
lock.lock(); // hold count: 2 — does NOT block
try {
System.out.println("Inner: " + lock.getHoldCount()); // 2
lock.lock(); // hold count: 3
try {
System.out.println("Deepest: " + lock.getHoldCount()); // 3
} finally {
lock.unlock(); // hold count: 2
}
} finally {
lock.unlock(); // hold count: 1
}
System.out.println("Back to outer: " + lock.getHoldCount()); // 1
} finally {
lock.unlock(); // hold count: 0 — lock fully released
}
// ── isHeldByCurrentThread: conditional acquisition ────────────────────
public class ResourceManager {
private final ReentrantLock lock = new ReentrantLock();
// Public API: always acquires the lock
public void processResource() {
lock.lock();
try {
doInternalWork(); // may call processResource() recursively — safe due to reentrancy
} finally {
lock.unlock();
}
}
// Can be called from processResource() (already locked) or standalone:
private void doInternalWork() {
// If called from processResource: isHeldByCurrentThread = true, getHoldCount >= 1
// If called standalone: isHeldByCurrentThread = false
System.out.println("Hold count: " + lock.getHoldCount());
// Assertion-style check — verify lock is held before internal work:
assert lock.isHeldByCurrentThread() : "doInternalWork must be called while holding lock";
// Work that requires the lock...
}
}
// ── Lock inspection methods ───────────────────────────────────────────
ReentrantLock inspectedLock = new ReentrantLock();
System.out.println("isLocked: " + inspectedLock.isLocked()); // false
System.out.println("isHeldByCurrentThread: "+ inspectedLock.isHeldByCurrentThread()); // false
System.out.println("getHoldCount: " + inspectedLock.getHoldCount()); // 0
System.out.println("getQueueLength: " + inspectedLock.getQueueLength()); // 0
System.out.println("isFair: " + inspectedLock.isFair()); // depends on constructor
inspectedLock.lock();
System.out.println("After lock():");
System.out.println(" isLocked: " + inspectedLock.isLocked()); // true
System.out.println(" isHeldByCurrentThread:"+ inspectedLock.isHeldByCurrentThread()); // true
System.out.println(" getHoldCount: " + inspectedLock.getHoldCount()); // 1
inspectedLock.unlock();
// hasQueuedThread and getQueueLength:
Thread waiter = new Thread(() -> {
inspectedLock.lock();
try { Thread.sleep(100); } catch (InterruptedException e) {}
finally { inspectedLock.unlock(); }
});
inspectedLock.lock(); // main thread holds lock
waiter.start();
Thread.sleep(50); // give waiter time to start waiting
System.out.println("Queue length: " + inspectedLock.getQueueLength()); // 1
System.out.println("Waiter queued: " + inspectedLock.hasQueuedThread(waiter)); // true
inspectedLock.unlock();
waiter.join();ReentrantLock vs synchronized — When to Use Each
// ── Decision: when synchronized is sufficient ────────────────────────
public class SimpleSafeCounter {
private int count = 0;
// synchronized is perfectly correct and simpler here:
public synchronized void increment() { count++; }
public synchronized int get() { return count; }
}
// ── Decision: ReentrantLock needed for timed acquisition ──────────────
public class ResourcePool {
private final Queue<Resource> available = new ArrayDeque<>();
private final ReentrantLock lock = new ReentrantLock();
public Optional<Resource> acquire(long timeoutMs) throws InterruptedException {
if (!lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
return Optional.empty(); // could not get pool lock — caller can try again
}
try {
return available.isEmpty()
? Optional.empty()
: Optional.of(available.poll());
} finally {
lock.unlock();
}
}
public void release(Resource r) {
lock.lock();
try { available.offer(r); }
finally { lock.unlock(); }
}
}
// ── Decision: ReentrantLock needed for interruptible wait ─────────────
public class CancellableProcessor {
private final ReentrantLock processingLock = new ReentrantLock();
public void process(Task task) throws InterruptedException {
processingLock.lockInterruptibly(); // can be cancelled while waiting
try {
task.execute();
} finally {
processingLock.unlock();
}
}
}
// ── Decision: fair lock for priority-sensitive work ───────────────────
public class FairWorkQueue {
private final Queue<Runnable> tasks = new ArrayDeque<>();
private final ReentrantLock lock = new ReentrantLock(true); // fair
private final Condition notEmpty = lock.newCondition();
public void submit(Runnable task) {
lock.lock();
try {
tasks.add(task);
notEmpty.signal();
} finally { lock.unlock(); }
}
// Fair lock ensures worker threads get work in submission order:
public Runnable take() throws InterruptedException {
lock.lock();
try {
while (tasks.isEmpty()) notEmpty.await();
return tasks.poll();
} finally { lock.unlock(); }
}
}
// ── Monitoring ReentrantLock for operational insight ──────────────────
public class MonitoredService {
private final ReentrantLock lock = new ReentrantLock();
private final AtomicLong blocked = new AtomicLong(0);
private final AtomicLong total = new AtomicLong(0);
public void doWork() {
total.incrementAndGet();
int queueDepthAtEntry = lock.getQueueLength();
if (queueDepthAtEntry > 0) blocked.incrementAndGet();
lock.lock();
try {
performWork();
} finally {
lock.unlock();
}
}
public double contentionRate() {
long t = total.get();
return t == 0 ? 0 : (double) blocked.get() / t;
}
}