Я внедряю параллельную банковскую систему, в которой все операции могут выполняться одновременно. Я реализовал потокобезопасный transferMoney
метод, который переводит amount
из Учетной записи from
в to
.
transferMoney
реализуется следующим кодом:
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;
}
Чтобы предотвратить взаимоблокировки, я указал, что блокировки всегда устанавливаются в одном и том же порядке. Чтобы обеспечить получение блокировок в том же порядке, я использую уникальный ID
из Account
.
Кроме того, я реализовал метод, который суммирует общую сумму денег в банке с помощью следующего кода:
public int sumAccounts(List<Account> accounts) {
AtomicInteger sum = new AtomicInteger();
synchronized(Account.class) {
for (Account a : accounts) {
sum.getAndAdd(a.getBalance());
}
}
return sum.intValue();
}
Проблема
Когда я запускаю sumAccounts()
одновременно с transferMoney()
, я получу больше (иногда меньше) денег в банке раньше, даже если деньги не добавлялись. Насколько я понимаю, если я заблокирую все Account
объекты через synchronized(Account.class)
, не должен ли я получить правильную сумму банка, поскольку я блокирую выполнение transferMoney()
?
Что я пробовал так далеко
Я пробовал следующее:
- синхронизация
Account.class
, как указано выше (не работает) - синхронизация конкретной учетной записи в цикле
for each
(но, конечно, это небезопасно для потоков, поскольку транзакции выполняются одновременно) - синхронизация обоих методов через объект
ReentrantLock
. Это работает, но сильно снижает производительность (занимает в три раза больше, чем последовательный код) - синхронизация обоих методов на уровне класса. Это тоже работает, но опять же занимает в три раза больше времени, чем выполнение операций последовательно.
Разве блокировка Account.class
не должна предотвращать дальнейшие transferMoney()
казни? Если нет, как я могу исправить эту проблему?
Изменить: код для getBalance()
:
public int getBalance() {
return balance;
}
Account.getBalance()
синхронизированный метод? В противном случае вы получите баланс непосредственно перед тем, как он будет вычтен из учетной записи и добавлен баланс только что добавленной учетной записи. - person Cratylus   schedule 06.04.2019transferMoney()
. Я добавил его для проверки, и он по-прежнему дает неправильный результат. - person pr0f3ss   schedule 06.04.2019getBalance()
? - person Cratylus   schedule 07.04.2019