Card 06/ 08
Lock Timeouts: Detecting a Deadlock Without Preventing It
tryLock with a timeout turns a hang into intermittent failures, and the cycle still forms.
Taking locks with a timeout, through ReentrantLock.tryLock, lets a thread give up instead of waiting forever. It does not stop the deadlock's cycle from forming; it only lets the threads recover after it has formed, by failing.
Two transfers between the same two accounts take their locks in opposite orders, the classic setup for a deadlock. With every lock taken under a five-second timeout, the service no longer hangs forever; instead it fails intermittently under load, because the locks are still taken in opposite orders:
if (fromLock.tryLock(5, SECONDS)) {
try {
if (toLock.tryLock(5, SECONDS)) {
try { move(pence); } finally { toLock.unlock(); }
} else {
throw new TransferTimedOut();
}
} finally { fromLock.unlock(); }
}The cycle still forms. Both threads take their first lock, both fail to get the second, and five seconds later both give up. What changed is what happens next: an exception instead of a hang.
| Compared on | Without a timeout | With one |
|---|---|---|
| The cycle forms | Yes | Yes |
| Threads recover | Never | After the timeout |
| The caller sees | Nothing, forever | A failure they have to handle |
| Under load | The process stops | Transfers fail intermittently |
So a timeout is a safety net, not a fix. Order the locks so the cycle cannot form, and keep the timeout for the case you did not think of.
← → move through sections and cards