想像一家跨国公司银行,它只想使用核心Java来实现帐户转账API,并且该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;
}
[想像一家跨国公司银行,它只想使用核心Java来实现帐户转帐API,并且该API将在多线程环境中使用,并在所有情况下保持帐户金额的一致性...
我想我们出于某种原因假设数据库事务不适用于该方案。因为对于数据库事务,这都不是必需的。