In Java, both synchronized and ReentrantLock are used to manage thread safety, but they offer different levels of control and flexibility.
1. synchronized Keyword
The synchronized keyword is the built-in mechanism in Java for mutual exclusion. It is simpler to use because the JVM automatically handles acquiring and releasing the lock.
- Usage: Can be applied to methods or used as a block.
- Automatic Release: The lock is automatically released when the thread exits the block or method, even if an exception occurs.
- Performance: In modern JVMs,
synchronizedis highly optimized (biased locking, lightweight locking) and often performs just as well asReentrantLock.
Example:
package org.kodejava.util.concurrent;
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public void incrementBlock() {
synchronized (this) {
count++;
}
}
}
2. ReentrantLock Class
ReentrantLock (from java.util.concurrent.locks) is an explicit lock implementation that provides advanced features not available with synchronized.
- Manual Control: You must explicitly call
lock()andunlock(). It is critical to use atry-finallyblock to ensure the lock is released. - Fairness: You can create a “fair” lock that gives preference to the longest-waiting thread.
- Try Lock:
tryLock()allows a thread to attempt to acquire the lock without blocking indefinitely. - Interruptible: A thread waiting for a
ReentrantLockcan be interrupted vialockInterruptibly().
Example:
package org.kodejava.util.concurrent;
import java.util.concurrent.locks.ReentrantLock;
public class LockCounter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
// Always unlock in finally to prevent deadlocks
lock.unlock();
}
}
}
Key Differences
| Feature | synchronized |
ReentrantLock |
|---|---|---|
| Ease of Use | Very easy; handled by JVM. | Requires manual try-finally. |
| Fairness | Not supported. | Supported (optional constructor parameter). |
| Flexibility | Rigid block structure. | Highly flexible (can lock in one method, unlock in another). |
| Non-blocking | No (thread always waits). | Yes, via tryLock(). |
| Interruptibility | No. | Yes, via lockInterruptibly(). |
Which should you choose?
- Use
synchronizedby default. It’s cleaner, less error-prone, and sufficient for most basic thread-safety needs. - Use
ReentrantLockwhen you need advanced features like:- Timing out while waiting for a lock (
tryLock(timeout)). - Allowing the lock attempt to be interrupted.
- Using a “Fair” lock strategy.
- Complex locking structures that aren’t strictly nested.
- Timing out while waiting for a lock (
If you are debugging concurrency issues, IntelliJ IDEA provides tools to inspect thread states and detect blocked monitors [1]. You can also use the Threads tab in the Debugger to see which thread owns a monitor or is waiting for one [2].
