I am implementing a parallel banking system, where all operations can run concurrently. I have implemented a thread safe transferMoney
method, that transfers amount
from Account from
to to
.
transferMoney
is implemented with the following code:
public boolean transferMoney(Account from, Account to, int amount) {
if (from.getId() == to.getId()){
return false;
}else if(from.getId() < to.getId()) {
synchronized(to) {
synchronized(from) {
if(from.getBalance() >= amount) {
from.setBalance(from.getBalance()-amount);
to.setBalance(to.getBalance()+amount);
}else {
return false;
}
}
}
}else {
synchronized(from) {
synchronized(to) {
if(from.getBalance() >= amount) {
from.setBalance(from.getBalance()-amount);
to.setBalance(to.getBalance()+amount);
}else {
return false;
}
}
}
}
return true;
}
To prevent deadlocks, I have specified that the locks are always acquired in the same order. To assure that the locks are acquired in the same order, I am using the unique ID
of Account
.
Additionally, I have implemented a method that sums up the total amount of money in the bank with the following code:
public int sumAccounts(List<Account> accounts) {
AtomicInteger sum = new AtomicInteger();
synchronized(Account.class) {
for (Account a : accounts) {
sum.getAndAdd(a.getBalance());
}
}
return sum.intValue();
}
Problem
When I run sumAccounts()
concurrently with transferMoney()
, I will end up with more (sometimes less) money in the bank before, even though no money was added. From my understanding if I lock all Account
objects via synchronized(Account.class)
, shouldn't I get the correct sum of the bank as I am blocking the execution of transferMoney()
?
What I have tried this far
I have tried the following things:
- synchronizing
Account.class
like above (doesn't work) - synchronizing the particular account in the
for each
loop (but of course this isn't thread safe as transactions are happening concurrently) - synchronizing both methods via a
ReentrantLock
object. This works, but it takes a huge hit on performance (takes three times as much as the sequential code) - synchronizing both methods on class level. This also works, but again takes three times longer than running the operations sequentially.
Shouldn't the lock on Account.class
prevent any further transferMoney()
executions? If not, how can I fix this issue?
Edit:
The code for getBalance()
:
public int getBalance() {
return balance;
}