I am wondering what are the alternative ways to avoid deadlock in the following example. The following example is a typical bank account transferring deadlock problem. What are some better approaches to solve it in practice ?
class Account {
double balance;
int id;
public Account(int id, double balance){
this.balance = balance;
this.id = id;
}
void withdraw(double amount){
balance -= amount;
}
void deposit(double amount){
balance += amount;
}
}
class Main{
public static void main(String [] args){
final Account a = new Account(1,1000);
final Account b = new Account(2,300);
Thread a = new Thread(){
public void run(){
transfer(a,b,200);
}
};
Thread b = new Thread(){
public void run(){
transfer(b,a,300);
}
};
a.start();
b.start();
}
public static void transfer(Account from, Account to, double amount){
synchronized(from){
synchronized(to){
from.withdraw(amount);
to.deposit(amount);
}
}
}
}
I am wondering will it solve the deadlock issue if I separate the nested lock out in my transfer method like the following
synchronized(from){
from.withdraw(amount);
}
synchronized(to){
to.deposit(amount);
}
You can also create separate lock for each Account (in Account class) and then before doing transaction acquire both locks. Take a look:
After get two locks you can do transfer without worrying about safe.
In addition to the solution of lock ordered you can also avoid the deadlock by synchronizing on a private static final lock object before performing any account transfers.
This solution has the problem that a private static lock restricts the system to performing transfers "sequentially".
Another one can be if each Account has a ReentrantLock:
Deadlock does not occur in this approach because those locks will never be held indefinitely. If the current object's lock is acquired but the second lock is unavailable, the first lock is released and the thread sleeps for some specified amount of time before attempting to reacquire the lock.
Here is the solution for the problem stated.
There are three requirements you must satisfy:
You can achieve 1. and 2. by using Atomics, but you will have to use something other that
double
as there is noAtomicDouble
.AtomicLong
would probably be your best bet.So you're left with your third requirement - if one succeeds the other must succeed. There is a simple technique that works superbly with atomics and that is using the
getAndAdd
methods.Sort the accounts. The dead lock is from the ordering of the accounts (a,b vs b,a).
So try:
This is a classic question. I see two possible solutions: