Synchronized and ReentrantLock in Java
In Java, both synchronized blocks/methods and ReentrantLock provide mechanisms for managing concurrent access to shared resources and ensuring thread safety. However, they differ in their features, flexibility, and usage.
Table of Contents
Synchronized
- Synchronized is a built-in Java keyword used to acquire and release locks for mutual exclusion.
- It provides inherent locking mechanism and is easy to use, as it automatically acquires and releases locks.
- Synchronization in Java is reentrant by default, meaning a thread can acquire the same lock multiple times without deadlocking itself (nested synchronized blocks).
- It is more suitable for simple synchronization requirements and straightforward code blocks/methods.
ReentrantLock
- ReentrantLock is a class in the java.util.concurrent.locks package that provides a flexible and explicit locking mechanism.
- It offers additional features compared to synchronized, such as support for interruptible locking, timed locking, and fairness policies.
- Unlike synchronized, ReentrantLock requires explicit acquisition and release of locks using lock() and unlock() methods, respectively.
- ReentrantLock allows more fine-grained control over locking, such as acquiring multiple locks, using tryLock() for non-blocking lock acquisition, and implementing custom locking strategies.
- It is more suitable for complex synchronization scenarios where fine-grained control over locking behavior is required.
Let’s demonstrate the difference between synchronized and ReentrantLock in Java with an example:
java
import java.util.concurrent.locks.ReentrantLock;
public class SynchronizedVsReentrantLockExample {
private static int count = 0;
private static final Object lock = new Object();
private static final ReentrantLock reentrantLock = new ReentrantLock();
// Synchronized method
public synchronized static void synchronizedMethod() {
count++;
}
// ReentrantLock method
public static void reentrantLockMethod() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// Using synchronized
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronizedMethod();
}
});
// Using ReentrantLock
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
reentrantLockMethod();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Using synchronized: " + count); // Expected: 20000
System.out.println("Using ReentrantLock: " + count); // Expected: 20000
}
}
Example
In this example, we have a shared variable count that we increment within synchronized and ReentrantLock methods. Both methods achieve mutual exclusion and thread safety, ensuring that count is incremented correctly. While synchronizedMethod() uses the intrinsic locking mechanism provided by synchronized, reentrantLockMethod() uses ReentrantLock for explicit locking. Both approaches yield the same result, demonstrating the difference in usage between synchronized and ReentrantLock.