避免死锁示例

pet*_*ter 18 java parallel-processing multithreading deadlock

我想知道在以下示例中有哪些替代方法可以避免死锁.以下示例是传输死锁问题的典型银行帐户.在实践中有哪些更好的解决方法?

class Account {
     double balance;
     int id;
     public Account(int id, double balance){
          this.balance = balance;
          this.id = id;
     }
     void withdraw(double amount){
          balance -= amount;
     } 
     void deposit(double amount){
          balance += amount;
     }
}
class Main{
     public static void main(String [] args){
           final Account a = new Account(1,1000);
           final Account b = new Account(2,300);
           Thread a = new Thread(){
                 public void run(){
                     transfer(a,b,200);
                 }
           };
           Thread b = new Thread(){
                 public void run(){
                     transfer(b,a,300);
                 }
           };
           a.start();
           b.start();
     }
     public static void transfer(Account from, Account to, double amount){
          synchronized(from){
               synchronized(to){
                    from.withdraw(amount);
                    to.deposit(amount);
               }
          }
     }
}
Run Code Online (Sandbox Code Playgroud)

我想知道它是否会解决死锁问题,如果我在我的传输方法中将嵌套锁分开,如下所示

 synchronized(from){
      from.withdraw(amount);
 }
 synchronized(to){
      to.deposit(amount);
 }
Run Code Online (Sandbox Code Playgroud)

Wil*_*ung 28

对帐户进行排序.死锁来自账户的排序(a,b vs b,a).

所以尝试:

 public static void transfer(Account from, Account to, double amount){
      Account first = from;
      Account second = to;
      if (first.compareTo(second) < 0) {
          // Swap them
          first = to;
          second = from;
      }
      synchronized(first){
           synchronized(second){
                from.withdraw(amount);
                to.deposit(amount);
           }
      }
 }
Run Code Online (Sandbox Code Playgroud)

  • 这在处理两个以上的账户时会起作用,对吗? (3认同)
  • @Piotr:不,在这种情况下,您可以根据帐户的特殊内容对帐户进行排序(例如帐号,或者DB中的主键等).实际排序无关紧要,只要它是所有参与者的稳定排序(即没有重复,如您所建议的那样). (3认同)

Bro*_*eal 8

这是一个经典的问题.我看到两种可能的解决方案

  1. 对帐户进行排序并在ID低于另一个帐户的帐户中进行同步.这个方法在第10章"并发Java并发实践"一书中提到过.在本书中,作者使用系统哈希码来区分帐户.请参阅java.lang.System#identityHashCode.
  2. 您提到了第二个解决方案 - 是的,您可以避免嵌套的同步块,并且您的代码不会导致死锁.但在这种情况下,处理可能会有一些问题,因为如果您从第一个帐户中提取资金,第二个帐户可能会在任何重要时间被锁定,并且您可能需要将钱存回第一个帐户.这并不好,因为嵌套同步和两个帐户的锁定是更好和更常用的解决方案.


dre*_*ash 7

除了有序锁定解决方案之外,您还可以通过在执行任何帐户转移之前同步私有静态最终锁定对象来避免死锁.

 class Account{
 double balance;
 int id;
 private static final Object lock = new Object();
  ....




 public static void transfer(Account from, Account to, double amount){
          synchronized(lock)
          {
                    from.withdraw(amount);
                    to.deposit(amount);
          }
     }
Run Code Online (Sandbox Code Playgroud)

该解决方案具有以下问题:专用静态锁限制系统"顺序地"执行传输.

如果每个帐户都有一个ReentrantLock,则可以是另一个:

private final Lock lock = new ReentrantLock();




public static void transfer(Account from, Account to, double amount)
{
       while(true)
        {
          if(from.lock.tryLock()){
            try { 
                if (to.lock.tryLock()){
                   try{
                       from.withdraw(amount);
                       to.deposit(amount);
                       break;
                   } 
                   finally {
                       to.lock.unlock();
                   }
                }
           }
           finally {
                from.lock.unlock();
           }

           int n = number.nextInt(1000);
           int TIME = 1000 + n; // 1 second + random delay to prevent livelock
           Thread.sleep(TIME);
        }

 }
Run Code Online (Sandbox Code Playgroud)

在这种方法中不会发生死锁,因为这些锁永远不会被无限期地保留.如果获取了当前对象的锁定但第二个锁定不可用,则释放第一个锁定,并且线程在尝试重新获取锁定之前会休眠一段指定的时间.


小智 5

您还可以为每个帐户创建单独的锁(在 Account 类中),然后在执行事务之前获取两个锁。看一看:

private boolean acquireLocks(Account anotherAccount) {
        boolean fromAccountLock = false;
        boolean toAccountLock = false;
        try {
            fromAccountLock = getLock().tryLock();
            toAccountLock = anotherAccount.getLock().tryLock();
        } finally {
            if (!(fromAccountLock && toAccountLock)) {
                if (fromAccountLock) {
                    getLock().unlock();
                }
                if (toAccountLock) {
                    anotherAccount.getLock().unlock();
                }
            }
        }
        return fromAccountLock && toAccountLock;
    }
Run Code Online (Sandbox Code Playgroud)

获得两把锁后,您可以进行转移而无需担心安全。

    public static void transfer(Acc from, Acc to, double amount) {
        if (from.acquireLocks(to)) {
            try {
                from.withdraw(amount);
                to.deposit(amount);
            } finally {
                from.getLock().unlock();
                to.getLock().unlock();
            }
        } else {
            System.out.println(threadName + " cant get Lock, try again!");
            // sleep here for random amount of time and try do it again
            transfer(from, to, amount);
        }
    }
Run Code Online (Sandbox Code Playgroud)