2013-06-24 135 views
2

假设有以下帐户类的两个对象 - account1和account2。有两个线程T1和T2。如何确保锁定顺序以避免死锁?

T1从帐户1转移量100到帐户2如下:

account1.transfer(account2, 100); 

类似地,T2从帐户2转移量为50〜帐户1:

account2.transfer(account1, 50); 

转印()方法显然是容易因为两个线程T1和T2试图以相反的顺序获取锁定而导致死锁。 (线程T1会先尝试获取帐户1上的锁定,然后是帐户2,而线程T2将尝试获取帐户2和帐户1上的锁定。)

确保锁定顺序为最佳方式(在此情况下)总是保证?

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); 
      } 
     } 
    } 
} 

回答

0

可以定义一个shared mutex锁定到,这样,当任何线程要进行交易,它试图收购该objact替代账户。如果一个线程锁定到这个共享对象上,那么你可以做一个事务。事务完成后,它可以释放该锁,以便另一个线程可以再次获取该对象。

+0

这是一个解决方案 - 谢谢。但是,如果有4个帐户a1到a4和4个线程T1到T4。 T1和T2与a1和a2一起工作。 T3和T4与a3和a4一起工作。我希望(T1和T2组)和(T3和T4组)同时工作,即这两组操作不需要等待锁定。 – Learner

+0

@Learner,这绝对不是问题,你只需要为每个帐号对组合定义一个互斥对象。在4个帐户的情况下,您可以在矢量中使用6个单独的互斥锁。 –

+0

因此,实现变得相当复杂,以推广互斥的排列和组合。另外,如果有100万个帐户,我无法想象需要多少个互斥体?!我们可能会对互斥量进行惰性初始化,但这会引入一组新的复杂性。 – Learner

1

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

然后传输被完全序列化,唯一的锁在队列中,所以死锁是不可能的。

我讨厌多个锁,正确命令或没有。

+0

感谢Martin回复。基本上,你提出了基于事件的机制来实现这一点 - 与你同意。但是,我发布这个问题的意图是要知道“如何确保锁定顺序?” – Learner

+0

是的 - 我不确定我的答复应该是答案还是评论,但决定答案。我可以争辩说,'我的解决方案通过将锁的数量减少到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尝试获取更多的锁或调用其他代码)时,你不能再确保正确的锁定顺序。

+0

这看起来像一个很好和简单的解决方案 - 谢谢!但是,我没有明白为什么你说这种方法只有在所有账户锁定都事先知道的情况下才可用。你能否详细说明一下? – Learner