How to ensure locking-order to avoid deadlock?

冷暖自知 提交于 2019-12-10 19:34:17

问题


Assume there are two objects of the following Account class - account1 and account2. And there are two threads T1 and T2.

T1 is transferring amount 100 from account1 to account2 as follows:

account1.transfer(account2, 100);

Similarly, T2 is transferring amount 50 from account2 to account1:

account2.transfer(account1, 50);

The transfer() method is obviously prone to deadlock as two threads T1 and T2 would be trying to acquire lock in the reverse order. (Thread T1 will try acquiring lock on account1 first and then account2. Whereas thread T2 will try acquiring lock on account2 and then on account1.)

What is the best way (in this case) to ensure locking-order to be guaranteed always?

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

回答1:


I would give only one thread access to the 'accounts' data. Any other thread that wants to transfer funds has to queue a 'transferRequest' object to it that contains the account IDs, the amount to be transferred an exception/errorMessage field and a callback/event, with the transferRequest as a parameter, for the thread to call when it has attempted the transaction.

The transfers are then serialized in their entirety, the only lock is in the queue, so deadlock is not possible.

I hate multiple locks, correctly ordered or no.




回答2:


You can implement ordering of the synchronized blocks yourself. Create a unique id for each account on creation and use synchronized in sorted order:

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

  }
}

But this approach is usable only if you know all the accounts to lock in advance.


Edit: I will try to clarify the part about knowing all the locks in advance.

In a simple exemaple like this, it is easy to collect all the needed locks, sort them and then lock them in correct order. The problem starts when your code gets more and more complicated and you try to use abstraction to keep the code readable. The lock ordering concept kind of goes aginst abstraction. When you call some encapsulated unknown code (which migth try to acquire more locks or call other code), you can no longer ensure correct lock ordering.




回答3:


You can define a shared mutex to lock onto so that when any of the threads want to make a transaction, it tries to acquire that objact instead of accounts. If a thread locks onto this shared object, then you can make a transaction. When transaction is finished it can release the lock so that another thread may acquire that object again.



来源:https://stackoverflow.com/questions/17268485/how-to-ensure-locking-order-to-avoid-deadlock

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!