Deep Dive into Java Concurrency: Practical ReentrantLock and Condition Usage
This article thoroughly compares Java's synchronized keyword with ReentrantLock, explains lock implementation details, fairness, interruptibility, timeout, and Condition usage, provides practical code examples and interview tips, and offers guidance on when to choose each synchronization mechanism.
1. ReentrantLock vs synchronized
Comparison of features: lock implementation (JVM vs AQS), lock release (automatic vs manual), interruptible acquisition (unsupported vs supported), timeout acquisition (unsupported vs supported via tryLock), fairness (not configurable vs configurable), condition support (single wait/notify vs multiple Condition objects), performance (early difference now similar).
2. Core usage of ReentrantLock
2.1 Basic usage
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock(); // acquire lock
try {
// business logic
} finally {
lock.unlock(); // must release in finally
}
}
}Unlike synchronized, ReentrantLock must be unlocked manually, typically in a finally block.
2.2 Fair vs unfair lock
// non‑fair (default)
ReentrantLock lock = new ReentrantLock();
// fair lock
ReentrantLock fairLock = new ReentrantLock(true);Fair lock grants access in request order, preventing starvation but reducing throughput; non‑fair lock allows barging, giving higher throughput at the risk of thread starvation.
Source code difference: non‑fair tryAcquire uses CAS without checking the queue; fair tryAcquire checks hasQueuedPredecessors() before CAS.
2.3 Interruptible acquisition
public void doSomething() throws InterruptedException {
lock.lockInterruptibly(); // can be interrupted while waiting
try {
// business logic
} finally {
lock.unlock();
}
}2.4 Timeout acquisition
public boolean tryDoSomething() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// business logic
return true;
} finally {
lock.unlock();
}
} else {
// timeout handling
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}3. Condition: multiple wait/notify queues
3.1 Why multiple Conditions?
wait/notify manages a single waiting queue per object, mixing all threads. Condition objects allow a lock to create independent queues, e.g., separate producer and consumer queues.
3.2 Producer‑consumer example
public class BoundedQueue<T> {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // queue not full
private final Condition notEmpty = lock.newCondition(); // queue not empty
private final LinkedList<T> queue = new LinkedList<>();
private final int capacity;
public BoundedQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await(); // wait if full
}
queue.addLast(element);
notEmpty.signal(); // wake consumer
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // wait if empty
}
T element = queue.removeFirst();
notFull.signal(); // wake producer
return element;
} finally {
lock.unlock();
}
}
}3.3 Condition vs wait/notify comparison
Waiting method: obj.wait() vs condition.await() Signal method: obj.notify() vs condition.signal() Multiple conditions: not supported vs multiple independent Condition objects
Interruptibility: not supported vs supported
Timeout waiting: not supported vs supported
Fairness: undefined vs configurable
4. Key points in ReentrantLock source
4.1 Reentrancy implementation
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}4.2 Unfair lock queue‑jumping
public boolean tryLock() {
return sync.nonfairTryAcquire(1); // CAS without queue check
}5. Usage recommendations
5.1 When to choose ReentrantLock
// need interruptible lock
lock.lockInterruptibly();
// need timeout
if (lock.tryLock(3, TimeUnit.SECONDS)) { … }
// need multiple conditions
Condition cond1 = lock.newCondition();
Condition cond2 = lock.newCondition();
// need fair lock (rare)
ReentrantLock fairLock = new ReentrantLock(true);5.2 When synchronized is sufficient
// simple scenario, concise code
public synchronized void method() { … }
// no need for timeout or interrupt
synchronized (lock) { … }6. Interview bonus answer
Key differences to mention:
Lock acquisition: synchronized is automatic; ReentrantLock requires explicit lock()/unlock().
Flexibility: ReentrantLock supports timeout, interruptibility, fairness, and multiple Condition objects.
Performance: early synchronized was slower; modern JVMs make the performance gap negligible.
Lock type: synchronized is a non‑fair JVM lock; ReentrantLock defaults to non‑fair but can be configured as fair.
Implementation: synchronized is built into the JVM; ReentrantLock is implemented on top of AbstractQueuedSynchronizer (AQS).
Guideline: prefer synchronized for simple cases; switch to ReentrantLock when advanced features are required.
7. Next article preview
Java concurrency deep dive (part 6): Thread‑pool source analysis – covering seven parameters, execution flow, worker design, and rejection policies.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
