如何确保锁定顺序以避免死锁?

时间:2013-06-24 05:20:17

标签: multithreading locking deadlock

假设以下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);
            }
        }
    }
}

3 个答案:

答案 0 :(得分:1)

我只给一个线程访问'accounts'数据。任何其他想要转移资金的线程都必须将'transferRequest'对象排入其中,该对象包含帐户ID,要传输的异常/ errorMessage字段和回调/事件的数量,以及transferRequest作为参数,用于该线程在尝试交易时调用。

然后传输完全序列化,唯一的锁在队列中,因此无法进行死锁。

我讨厌多个锁,正确订购或没有。

答案 1 :(得分: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);
      }
    }

  }
}

但只有当您知道要提前锁定的所有帐户时,此方法才可用。


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

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

答案 2 :(得分:0)

您可以定义要锁定的shared mutex,以便当任何线程想要进行交易时,它会尝试获取该objact而不是帐户。如果线程锁定到此共享对象,则可以进行事务。当事务完成时,它可以释放锁,以便另一个线程可以再次获取该对象。