Explicit Locks in Java — ReentrantLock
The synchronized keyword provides a simple way to achieve thread safety, but it has limitations — lack of flexibility, fairness control, and advanced features.
Java's Lock interface (in java.util.concurrent.locks) and its primary implementation ReentrantLock provide more powerful synchronization mechanisms.
1. Introduction to Lock Interface
Lockis an alternative to intrinsic locks (monitors used bysynchronized).- Provides explicit methods to acquire and release locks (
lock(),unlock()). - Offers advanced features like fairness, timed lock acquisition, and multiple condition variables.
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SharedResource {
private final Lock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock(); // Acquire lock
try {
counter++;
} finally {
lock.unlock(); // Always release in finally
}
}
public int getCounter() {
return counter;
}
}2. ReentrantLock Features
2.1 Basic Lock/Unlock
- lock() acquires the lock, blocking if unavailable.
- unlock() releases it.
⚠️ Must always callunlock()in afinallyblock to avoid deadlocks.
2.2 tryLock()
- Attempts to acquire lock without blocking indefinitely.
- Returns
trueif lock acquired,falseotherwise.
java
if (lock.tryLock()) {
try {
// Critical section
} finally {
lock.unlock();
}
} else {
// Could not acquire lock, do something else
}2.3 Timeout Lock
java
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// work
} finally {
lock.unlock();
}
}2.4 Fair vs Non-Fair Locks
- By default,
ReentrantLockis non-fair (threads may barge in). - With fairness set to true:
new ReentrantLock(true), threads acquire in FIFO order. - Fair locks reduce starvation but are slower.
2.5 Condition Variables
Conditionobjects (vialock.newCondition()) allow fine-grained thread communication.- Similar to
wait()/notify(), but more flexible.
java
import java.util.concurrent.locks.Condition;
class BoundedBuffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final int[] items = new int[5];
private int count, putPtr, takePtr;
public void put(int x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) notFull.await();
items[putPtr] = x;
putPtr = (putPtr + 1) % items.length;
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int take() throws InterruptedException {
lock.lock();
try {
while (count == 0) notEmpty.await();
int x = items[takePtr];
takePtr = (takePtr + 1) % items.length;
count--;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}3. Example: Producer-Consumer with ReentrantLock
java
class ProducerConsumer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final int[] buffer = new int[5];
private int count, putPtr, takePtr;
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) notFull.await();
buffer[putPtr] = value;
putPtr = (putPtr + 1) % buffer.length;
count++;
System.out.println("Produced: " + value);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (count == 0) notEmpty.await();
int value = buffer[takePtr];
takePtr = (takePtr + 1) % buffer.length;
count--;
System.out.println("Consumed: " + value);
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Runnable producer = () -> {
for (int i = 0; i < 10; i++) {
try {
pc.produce(i);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
Runnable consumer = () -> {
for (int i = 0; i < 10; i++) {
try {
pc.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
new Thread(producer).start();
new Thread(consumer).start();
}
}4. Interview Focus: Synchronized vs ReentrantLock
| Aspect | synchronized | ReentrantLock |
|---|---|---|
| Acquisition | Implicit (monitor) | Explicit (lock()) |
| Release | Automatic | Manual (unlock() required) |
| Fairness | Not possible | Fair or non-fair |
| Try/Timeout Lock | Not available | Available (tryLock()) |
| Condition Variables | Single (wait/notify) | Multiple (Condition) |
| Readability | Simple | Verbose but flexible |
Conclusion
ReentrantLockprovides greater control thansynchronized, with features like fairness, tryLock, and conditions.- However, it requires manual discipline (must unlock).
- For most cases,
synchronizedis enough. UseReentrantLockwhen advanced features are needed.