4

假设有以下 Account 类的两个对象 - account1 和 account2。并且有两个线程T1和T2。

T1 正在将金额 100 从 account1 转移到 account2,如下所示:

account1.transfer(account2, 100);

同样,T2 将金额 50 从 account2 转移到 account1:

account2.transfer(account1, 50);

transfer() 方法显然容易出现死锁,因为两个线程 T1 和 T2 会尝试以相反的顺序获取锁。(线程 T1 将首先尝试获取 account1 的锁,然后是 account2。而线程 T2 将尝试获取 account2 的锁,然后是 account1。)

确保始终保证锁定顺序的最佳方法是什么(在这种情况下)?

public class Account {
    private float balance;

    public class Account() {
        balance = 5000f;
    }

    private void credit(float amt) {
        balance += amt;
    }

    // To exclude noise assume the balance will never be negative
    private void debit(float amt) {
        balance -= amt;
    }

    // Deadlock prone as the locking order is not guaranteed
    public void transfer(Account acc2, float amt) {
        synchronized(this) {
            synchronized(acc2) {
                acc2.debit(amt);
                this.credit(amt);
            }
        }
    }
}
4

3 回答 3

1

我只会让一个线程访问“帐户”数据。任何其他想要转移资金的线程都必须向其排队一个“transferRequest”对象,该对象包含帐户 ID、要转移的金额、异常/errorMessage 字段和回调/事件,以 transferRequest 作为参数,用于线程在尝试交易时调用。

然后将传输全部序列化,唯一的锁在队列中,因此不可能出现死锁。

我讨厌多个锁,无论是否正确订购。

于 2013-06-24T06:22:21.357 回答
1

您可以自己实现同步块的排序。在创建时为每个帐户创建一个唯一 ID,并按排序顺序使用同步:

class Account {

  private float balance;
  private final int id;
  private static AtomicInteger idGen = new AtomicInteger(0);

  public Account() {
    id = idGen.incrementAndGet();
    balance = 5000f;
  }

  private void credit(float amt) {
    balance += amt;
  }

  // To exclude noise assume the balance will never be negative
  private void debit(float amt) {
    balance -= amt;
  }

  // Deadlock prone as the locking order is not guaranteed
  public void transfer(Account acc2, float amt) {
    Account first = this.id > acc2.id ? acc2 : this;
    Account second = this.id > acc2.id ? this : acc2;

    synchronized (first) {
      synchronized (second) {
        acc2.debit(amt);
        this.credit(amt);
      }
    }

  }
}

但这种方法只有在您提前知道要锁定的所有帐户时才可用。


编辑:我将尝试澄清有关提前了解所有锁的部分。

在像这样的简单示例中,很容易收集所有需要的锁,对它们进行排序,然后以正确的顺序锁定它们。当您的代码变得越来越复杂并且您尝试使用抽象来保持代码可读性时,问题就开始了。锁排序概念有点反对抽象。当您调用一些封装的未知代码(可能会尝试获取更多锁或调用其他代码)时,您将无法再确保正确的锁顺序。

于 2013-06-24T09:05:05.180 回答
0

您可以定义 ashared mutex锁定,以便当任何线程想要进行事务时,它会尝试获取该对象而不是帐户。如果一个线程锁定了这个共享对象,那么您可以进行事务。事务完成后,它可以释放锁,以便另一个线程可以再次获取该对象。

于 2013-06-24T05:28:28.720 回答