The full picture
| Capability | synchronized | ReentrantLock | ReadWriteLock |
|---|---|---|---|
| Auto-release on exception | ✅ always | ❌ needs finally | ❌ needs finally |
| Try / timed / interruptible acquire | ❌ | ✅ | ✅ |
| Fairness option | ❌ | ✅ (throughput cost) | ✅ |
| Multiple wait-sets | one (wait/notify) | ✅ many Conditions | — |
| Concurrent readers | ❌ | ❌ | ✅ read lock shared |
private final ReentrantLock lock = new ReentrantLock();
void transfer(Account from, Account to, long amount) throws TimeoutException {
if (!lock.tryLock(2, TimeUnit.SECONDS)) // deadlock-resistant
throw new TimeoutException("busy, try later");
try {
// critical section
} finally {
lock.unlock(); // NEVER forget
}
}Deadlock needs four conditions (mutual exclusion, hold-and-wait, no preemption, circular wait); you only have to break one. In practice: acquire locks in one global order (e.g., lock accounts by ascending id), keep critical sections tiny, never call foreign/user code while holding a lock, and prefer tryLock with timeout at integration boundaries. Detect with jstack (it prints found deadlocks) or ThreadMXBean.findDeadlockedThreads().