Card 04/ 08
All 8 cards
WalkthroughDifficulty: Advanced1 min
Fixing the Transfer by Giving the Two Locks an Order
The transfer method, made deadlock-free by changing nothing about what it locks and everything about the order.
From a method that can deadlock to one that cannot
Step 1 of 4
Find something to order the locks by
Any total order will do, as long as every thread agrees. An account number, a database id, a name — anything that is fixed for the life of the object and never equal for two different objects.
record Account(long id, long pence) { }Sort the two locks before taking either
void transfer(Account from, Account to, long pence) {
Account first = from.id() < to.id() ? from : to;
Account second = from.id() < to.id() ? to : from;
synchronized (first) {
synchronized (second) {
from.withdraw(pence);
to.deposit(pence);
}
}
}The locks are now taken in id order regardless of which way the transfer goes. Both threads in the earlier example take Alice's lock first, so one waits for the other and then proceeds.
Handle the case where both are the same object
if (from.id() == to.id()) {
throw new IllegalArgumentException("cannot transfer to the same account");
}Without this, first and second are the same account and the inner lock is a reentrant acquisition — harmless here, and a sign that the caller meant something else.
Check the cycle cannot form
| Thread | Takes first | Takes second |
|---|---|---|
| Alice to Bob | Alice, the lower id | Bob |
| Bob to Alice | Alice, the lower id | Bob |
Both rows are identical, so there is no arrangement in which one thread holds Bob's lock while waiting for Alice's. The circular-wait condition has been removed, and the other three are untouched.
The run to remember: the fix is not more locking or shorter locking. It is agreeing on an order, and applying the same order everywhere in the program that takes both locks.