我创建了一个小应用程序,该应用程序主要包括用于在账户之间进行转账的剩余Apis。为了简单起见,我将并发哈希图用于数据存储。现在,要实现的最基本概念是多线程。
由于我没有使用任何数据库,因此我使用可重入锁在Account类本身上创建了锁。现在,当我进行交易时,我同时获得了两个帐户对象(即发送者和接收者)的锁定,然后将其传递给biconsumer.accept()。
我不确定这是否完全正确(仅就此应用而言)。 也不确定如何对此进行单元测试。
Account.java
public class Account {
private ReentrantLock lock = new ReentrantLock();
private String accountNumber;
private long accountBalance;
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public long getAccountBalance() {
return accountBalance;
}
public void setAccountBalance(long accountBalance) {
this.accountBalance = accountBalance;
}
public void getLock() {
this.accountLock.lock();
}
public void doUnlock() {
this.accountLock.unlock();
}
}
Transfer.java
send(senderAccount, receiverAccount, (x, y) -> {
senderAccount.setAccountBalance(senderAccount.getAccountBalance() - (transferAmount));
receiverAccount.setAccountBalance(toAccountDto.getAccountBalance() + transferAmount));
});
public void send(Account senderAccount, Account receiverAccount,
BiConsumer<Account, Account> action) {
senderAccount.lock();
try {
receiverAccount.lock();
try {
biConsumer.accept(senderAccount, receiverAccount);
} finally {
receiverAccount.unlock();
}
} finally {
senderAccount.unlock();
}
}
就单线程而言,这可以按预期工作。 但是我该如何对它进行单元测试,以检查它是否可以在10000个线程中正常工作。
这个帐户级别是否也锁定了良好的做法(就此应用而言),还是我可以做其他事情?
答案 0 :(得分:1)
锁是一个不好的工具。
在您发布的示例中,如果两个线程在两个帐户之间汇款,则很有可能会陷入僵局。
final Account acc1 = new Account();
final Account acc2 = new Account();
new Thread(() -> {
while (true) {
send(acc1, acc2, (x, y) -> {
x.setAccountBalance(x.getAccountBalance() - 100);
y.setAccountBalance(y.getAccountBalance() + 100);
});
}
}).start();
new Thread(() -> {
while (true) {
send(acc2, acc1, (x, y) -> {
x.setAccountBalance(x.getAccountBalance() - 100);
y.setAccountBalance(y.getAccountBalance() + 100);
});
}
}).start();
在某个时候,线程1将锁定acc1
,而线程2将锁定锁定acc2
。
防止这种情况的通常方法是具有一定顺序的获取锁。
在这种情况下,太多的锁无法管理。
更好的解决方案是使用AtomicLong
,但这需要对代码进行一些更改。
public class Account {
private String accountNumber;
private AtomicLong accountBalance = new AtomicLong();
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public long getAccountBalance() {
return accountBalance.get();
}
/* Don't use this method if you expect that the balance has not changed since the last get */
public void setAccountBalance(long accountBalance) {
this.accountBalance.set(accountBalance);
}
/* Changes the balance atomically */
public void addAccountBalance(long amountToAdd) {
accountBalance.addAndGet(amountToAdd);
}
}
然后您可以像这样使用它:
senderAccount.addAccountBalance(-sendAmount);
receiverAccount.addAccountBalance(sendAmount);
因为没有锁,所以不会发生死锁。 虽然每个动作都是原子动作,但有一些警告:
compareAndSet
。编写多线程代码时最大的问题是找出要保证的内容-还是仍然保持一致的状态。然后确保没有线程看到系统处于不一致状态。