Card 04/ 08

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.

java
record Account(long id, long pence) { }

Sort the two locks before taking either

java
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

java
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

What each thread now takes, in order
ThreadTakes firstTakes second
Alice to BobAlice, the lower idBob
Bob to AliceAlice, the lower idBob

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.