我碰到这个问题,近日来到这里,我假设找到现在下面的代码僵局。我没有Java或多线程的经验,这就是为什么我在这里更好地理解问题的原因。
public class BankAccount {
private final int customerId;
private int balance;
public BankAccount(int customerId, int openingBalance) {
this.customerId = customerId;
this.balance = openingBalance;
}
public void withdraw(int amount) throws OverdrawnException {
if (amount > balance) {
throw new OverdrawnException();
}
balance -= amount;
}
public void deposit(int amount) {
balance += amount;
}
public int getCustomerId() {
return customerId;
}
public int getBalance() {
return balance;
}
}
class TransferOperation extends Thread {
int threadNum;
TransferOperation(int threadNum) {
this.threadNum = threadNum;
}
private void transfer(BankAccount fromAccount, BankAccount toAccount, int transferAmount) throws OverdrawnException {
synchronized (fromAccount) {
synchronized (toAccount) {
fromAccount.withdraw(transferAmount);
toAccount.deposit(transferAmount);
}
}
}
// ...
}
我有这样的上面一段代码。我想找到以上代码中可能发生死锁的位置。我认为它可能发生的唯一位置是在两个同步块中。我说的对吗?
如果是,有人可以帮助我了解为什么?我可以猜测,很可能是因为退出,存款保持一个线程等待另一个。但这是一个总的猜测,这就是为什么在这里寻求帮助。
如果我想防止死锁,我该如何在代码中解决死锁?
任何帮助表示赞赏。
答案 0 :(得分:3)
您要先在fromAccount
个对象上同步,然后在toAccount
个对象上同步。
想象一下,第一个线程使用X,Y调用传递方法,而同时另一个线程使用Y,X参数调用相同的方法-这会导致死锁。
第一个线程“锁定” X,第二个线程“锁定” Y,然后第一个线程试图锁定Y,后者已经被第二个线程锁定。
但是第二个线程正在尝试锁定已经被第一个线程锁定的X。
这是死锁。
答案 1 :(得分:1)
以下是我可能想到的解决方案。
可能的解决方案:
在transfer()
内始终先锁定ID较小的帐户,然后再锁定ID较大的帐户。
检查以下内容以查看其工作原理:Would you explain lock ordering?
将整个传输方式标记为synchronized
。
它将使它成为单线程的,可以解决此问题,但是对于大量帐户来说速度很慢。
将请求封装为一个对象,将其发送到FIFO队列,然后一个线程处理器可以处理它,每个请求都在事务中处理。
(根据解决方案2进行了改进),在内存中维护一组正在处理的请求的帐户ID。
(此实现可能很复杂,但是正确编写起来并不容易)
对请求进行分组。
在每个组中,任何ID仅出现一次,然后逐个处理组。
一次只能处理一组,但要处理多个线程。
(这与第3种解决方案有点相似,但对于正确实现而言并非易事)。