Java > Concurrency and Multithreading > Synchronization and Locks > Reentrant Locks
ReentrantLock Example
This example demonstrates how to use a ReentrantLock
to protect a shared resource from concurrent access. ReentrantLock
provides more flexibility than synchronized blocks, allowing for features like fairness and interruptibility.
Code Example
This code defines a Counter
class that uses a ReentrantLock
to protect its count
variable. The increment()
method acquires the lock before incrementing the count and releases it in a finally
block to ensure the lock is always released, even if an exception occurs. The main
method creates two threads that increment the counter concurrently. The `join()` method waits for the threads to finish executing before printing the final count.
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // Acquire the lock
try {
count++;
System.out.println(Thread.currentThread().getName() + ": Count is: " + count);
} finally {
lock.unlock(); // Release the lock in a finally block
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
public class ReentrantLockExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task, "Thread-1");
Thread thread2 = new Thread(task, "Thread-2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final Count: " + counter.getCount());
}
}
Concepts Behind the Snippet
ReentrantLock: A reentrant lock is a synchronization primitive that allows a thread to acquire the same lock multiple times without blocking. This is in contrast to a basic lock, where a thread would block if it tried to acquire a lock it already held.
Locking and Unlocking: The lock()
method acquires the lock, and the unlock()
method releases it. It is crucial to release the lock in a finally
block to prevent deadlocks in case of exceptions.
Fairness: ReentrantLocks can be configured to be 'fair', meaning threads are granted access to the lock in the order they requested it. This prevents thread starvation but can reduce overall throughput.
Thread Safety: Guarantees that multiple threads can safely access and modify shared data without causing data corruption or race conditions.
Real-Life Use Case
Consider a banking application where multiple threads might try to update the balance of an account simultaneously. A ReentrantLock
can be used to ensure that only one thread can access and modify the balance at a time, preventing race conditions and ensuring data integrity.
Best Practices
finally
block: This ensures that the lock is always released, even if an exception occurs.
Interview Tip
Be prepared to explain the differences between ReentrantLock
and synchronized blocks. Highlight the flexibility and advanced features of ReentrantLock
, such as fairness, interruptibility, and the ability to try to acquire the lock without blocking.
When to Use Reentrant Locks
Use ReentrantLock
when you need more control over locking than what synchronized blocks offer. This includes situations where you need fairness, interruptibility, or the ability to try to acquire the lock without blocking. They are particularly useful for complex concurrent scenarios where standard synchronization mechanisms are insufficient.
Memory Footprint
ReentrantLock
introduces a slight memory overhead compared to intrinsic locks (synchronized keyword). The object itself has to store the state of the lock, including the owner thread and the hold count. However, this overhead is usually negligible compared to the complexity they help manage.
Alternatives
Pros
tryLock()
method for non-blocking lock attempts.
Cons
FAQ
-
What is the difference between ReentrantLock and synchronized?
ReentrantLock
provides more flexibility and advanced features compared to synchronized blocks, such as fairness, interruptibility, and the ability to try to acquire the lock without blocking. Synchronized blocks are simpler and more basic, but they lack these advanced features. -
Why should I release the lock in a
finally
block?
Releasing the lock in afinally
block ensures that the lock is always released, even if an exception occurs. This prevents deadlocks and ensures that other threads can access the shared resource. -
What is fairness in ReentrantLock?
Fairness inReentrantLock
means that threads are granted access to the lock in the order they requested it. This prevents thread starvation but can reduce overall throughput. -
What is the difference between lock() and tryLock()?
Thelock()
method blocks until the lock is acquired. ThetryLock()
method attempts to acquire the lock immediately and returnstrue
if the lock is acquired orfalse
if it is not. ThetryLock(long time, TimeUnit unit)
method attempts to acquire the lock and waits for a specified time to acquire it.