Представьте себе банк MNC, который хочет реализовать API перевода счета, используя только базовую Java, а API будет использоваться в многопоточной среде и поддерживать согласованность суммы счета все время без тупика, конечно.
Я имею в виду два подхода к реализации этого и знаю их плюсы и минусы, как описано ниже, но не могу измерить, какие плюсы здесь более хороши в случае параллелизма или когда у нас больше нагрузки. Или какие минусы должны влиять больше в случае параллелизма или когда у нас больше нагрузки.
Пожалуйста, посоветуйте и предоставьте свое предложение.
Ссылка на репозиторий Github с полным кодом https://github.com/sharmama07/money-transfer
Прототип API: public boolean transferAmount(Integer fromAccountId, Integer toAccountId, Integer amount);
Подход 1: обработка параллелизма с помощью цикла while и оператора SQL для проверки предыдущего баланса в предложении where. Если предыдущий баланс не совпадает, вызов суммы обновления в учетной записи завершится ошибкой, и он извлечет последнюю сумму учетной записи из БД и попытается обновить ее снова, пока не обновит ее успешно. Здесь ни один поток не будет заблокирован, что означает отсутствие вероятности взаимоблокировки, отсутствие накладных расходов на приостановку потока и отсутствие задержки потока. Но у него может быть еще несколько вызовов БД
public boolean transferAmount(Integer fromAccountId, Integer toAccountId, Double amount) {
boolean updated = false;
try {
while(!updated) {
Account fromAccount = accountDAO.getAccount(fromAccountId);
if(fromAccount.getAmount()-amount < 0) {throw new OperationCannotBePerformedException("Insufficient balance!");}
int recordsUpdated = accountDAO.updateAccountAmount(fromAccount.getId(), fromAccount.getAmount(),
fromAccount.getAmount()-amount);
updated = (recordsUpdated==1);
}
}catch (OperationCannotBePerformedException e) {
LOG.log(Level.SEVERE, "Debit Operation cannot be performed, because " + e.getMessage());
}
if(updated) {
updated = false;
try {
while(!updated) {
Account toAccount = accountDAO.getAccount(toAccountId);
int recordsUpdated = accountDAO.updateAccountAmount(toAccount.getId(), toAccount.getAmount(), toAccount.getAmount()+amount);
updated = (recordsUpdated==1);
}
}catch (OperationCannotBePerformedException e) {
LOG.log(Level.SEVERE, "Credit Operation cannot be performed, because " + e.getMessage());
revertDebittransaction(fromAccountId, amount);
}
}
return updated;
}
// Account DAO call
@Override
public Account getAccount(Integer accountId) throws OperationCannotBePerformedException {
String SQL = "select id, amount from ACCOUNT where id="+accountId+"";
ResultSet rs;
try {
rs = statement.executeQuery(SQL);
if (rs.next()) {
return new Account(rs.getInt(1), rs.getDouble(2));
}
return null;
} catch (SQLException e) {
LOG.error("Cannot retrieve account from DB, reason: "+ e.getMessage());
throw new OperationCannotBePerformedException("Cannot retrieve account from DB, reason: "+ e.getMessage(), e);
}
}
@Override
public int updateAccountAmount(Integer accountId, Double currentAmount, Double newAmount) throws OperationCannotBePerformedException {
String SQL = "update ACCOUNT set amount=" + newAmount +" where id="+accountId+" and amount="+currentAmount+"";
int rs;
try {
rs = statement.executeUpdate(SQL);
return rs;
} catch (SQLException e) {
LOG.error("Cannot update account amount, reason: "+ e.getMessage());
throw new OperationCannotBePerformedException("Cannot update account amount, reason: "+ e.getMessage(), e);
}
}
Подход 2: Здесь другие потоки будут заблокированы, если одна и та же учетная запись находится в двух транзакциях в разных потоках,
но будет меньше вызовов БД
public boolean transferAmount1(Integer fromAccountId, Integer toAccountId, Double amount) {
boolean updated = false;
Integer smallerAccountId = (fromAccountId<toAccountId)? fromAccountId: toAccountId;
Integer largerAccountId = (fromAccountId<toAccountId)? toAccountId:fromAccountId;
synchronized(smallerAccountId) {
synchronized(largerAccountId) {
try {
Account fromAccount = accountDAO.getAccount(fromAccountId);
if(fromAccount.getAmount()-amount < 0) {
throw new OperationCannotBePerformedException("Insufficient balance!");
}
int recordsUpdated = accountDAO.updateAccountAmount(fromAccount.getId(),
fromAccount.getAmount(), fromAccount.getAmount()-amount);
updated = (recordsUpdated==1);
}catch (OperationCannotBePerformedException e) {
LOG.log(Level.SEVERE, "Debit Operation cannot be performed, because " + e.getMessage());
}
// credit operation
if(updated) {
try {
updated = false;
Account toAccount = accountDAO.getAccount(toAccountId);
int recordsUpdated = accountDAO.updateAccountAmount(toAccount.getId(),
toAccount.getAmount(), toAccount.getAmount()+amount);
updated = (recordsUpdated==1);
}catch (OperationCannotBePerformedException e) {
LOG.log(Level.SEVERE, "Credit Operation cannot be performed, because " + e.getMessage());
revertDebittransaction(fromAccountId, amount);
}
}
}
}
return updated;
}
select ... for update
см. stackoverflow.com/questions/46995155/ - person Scary Wombat   schedule 27.01.2020