java 避免死锁示例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13326861/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 12:18:41  来源:igfitidea点击:

Avoid Deadlock example

javamultithreadingparallel-processingdeadlock

提问by peter

I am wondering what are the alternative ways to avoid deadlock in the following example. The following example is a typical bank account transferring deadlock problem. What are some better approaches to solve it in practice ?

我想知道在以下示例中避免死锁的替代方法是什么。下面的例子是一个典型的银行账户转账死锁问题。在实践中解决它的更好方法是什么?

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

I am wondering will it solve the deadlock issue if I separate the nested lock out in my transfer method like the following

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

 synchronized(from){
      from.withdraw(amount);
 }
 synchronized(to){
      to.deposit(amount);
 }

回答by Will Hartung

Sort the accounts. The dead lock is from the ordering of the accounts (a,b vs b,a).

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

So try:

所以尝试:

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

回答by BrownFurSeal

This is a classic question. I see two possible solutions:

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

  1. To sort accounts and synchronize at account which has an id lower than another one. This method mentioned in the bible of concurrency Java Concurrency in Practice in chapter 10. In this book authors use system hash code to distinguish the accounts. See java.lang.System#identityHashCode.
  2. The second solution is mentioned by you - yes you can avoid nested synchronized blocks and your code will not lead to deadlock. But in that case the processing might have some problems because if you withdraw money from the first account the second account may be locked for any significant time and probably you will need to put money back to the first account. That's not good and because that nested synchronization and the lock of two accounts is better and more commonly used solution.
  1. 对帐户进行排序并在 id 低于另一个帐户的帐户上进行同步。这种方法在《Java Concurrency in Practice》的并发圣经第10章中提到过。本书作者使用系统哈希码来区分账户。参见java.lang.System#identityHashCode
  2. 您提到了第二种解决方案 - 是的,您可以避免嵌套的同步块,并且您的代码不会导致死锁。但在这种情况下,处理过程可能会出现一些问题,因为如果您从第一个帐户取款,第二个帐户可能会被锁定很长时间,您可能需要将钱放回第一个帐户。那不好,因为嵌套同步和两个帐户的锁定是更好和更常用的解决方案。

回答by dreamcrash

In addition to the solution of lock ordered you can also avoid the deadlock by synchronizing on a private static final lock object before performing any account transfers.

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

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

This solution has the problem that a private static lock restricts the system to performing transfers "sequentially".

该解决方案存在私有静态锁限制系统“按顺序”执行传输的问题。

Another one can be if each Account has a ReentrantLock:

如果每个帐户都有一个 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);
        }

 }

Deadlock does not occur in this approach because those locks will never be held indefinitely. If the current object's lock is acquired but the second lock is unavailable, the first lock is released and the thread sleeps for some specified amount of time before attempting to reacquire the lock.

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

回答by Mafias

You can also create separate lock for each Account (in Account class) and then before doing transaction acquire both locks. Take a look:

您还可以为每个帐户创建单独的锁(在 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;
    }

After get two locks you can do transfer without worrying about safe.

拿到两把锁后就可以转账了,不用担心安全。

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

回答by Shaan

Here is the solution for the problem stated.

以下是所述问题的解决方案。

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class FixDeadLock1 {

    private class Account {

        private final Lock lock = new ReentrantLock();

        @SuppressWarnings("unused")
        double balance;
        @SuppressWarnings("unused")
        int id;

        public Account(int id, double balance) {
            this.balance = balance;
            this.id = id;
        }

        void withdraw(double amount) {
            this.balance -= amount;
        }

        void deposit(double amount) {
            balance += amount;
        }
    }

    private class Transfer {

        void transfer(Account fromAccount, Account toAccount, double amount) {
            /*
             * synchronized (fromAccount) { synchronized (toAccount) {
             * fromAccount.withdraw(amount); toAccount.deposit(amount); } }
             */

            if (impendingTransaction(fromAccount, toAccount)) {
                try {
                    System.out.format("Transaction Begins from:%d to:%d\n",
                            fromAccount.id, toAccount.id);
                    fromAccount.withdraw(amount);
                    toAccount.deposit(amount);
                } finally {
                    fromAccount.lock.unlock();
                    toAccount.lock.unlock();
                }

            } else {
                 System.out.println("Unable to begin transaction");
            }

        }

        boolean impendingTransaction(Account fromAccount, Account toAccount) {

            Boolean fromAccountLock = false;
            Boolean toAccountLock = false;

            try {
                fromAccountLock = fromAccount.lock.tryLock();
                toAccountLock = toAccount.lock.tryLock();
            } finally {
                if (!(fromAccountLock && toAccountLock)) {
                    if (fromAccountLock) {
                        fromAccount.lock.unlock();
                    }
                    if (toAccountLock) {
                        toAccount.lock.unlock();
                    }
                }
            }

            return fromAccountLock && toAccountLock;
        }

    }

    private class WrapperTransfer implements Runnable {
        private Account fromAccount;
        private Account toAccount;
        private double amount;

        public WrapperTransfer(Account fromAccount,Account toAccount,double amount){
            this.fromAccount = fromAccount;
            this.toAccount = toAccount; 
            this.amount = amount;
        }

        public void run(){
            Random random = new Random();
            try {
                int n = random.nextInt(1000);
                int TIME = 1000 + n; // 1 second + random delay to prevent livelock
                Thread.sleep(TIME);
            } catch (InterruptedException e) {}
            new Transfer().transfer(fromAccount, toAccount, amount);
        }

    }

    public void initiateDeadLockTransfer() {
        Account from = new Account(1, 1000);
        Account to = new Account(2, 300);       
        new Thread(new WrapperTransfer(from,to,200)).start();
        new Thread(new WrapperTransfer(to,from,300)).start();
    }

    public static void main(String[] args) {
        new FixDeadLock1().initiateDeadLockTransfer();
    }

}

回答by OldCurmudgeon

There are three requirements you must satisfy:

您必须满足三个要求:

  1. Consistently reduce the contents of one account by the specified amount.
  2. Consistently increase the contents of the other account by the specified amount.
  3. If one of the above is successful, the other must also be successful.
  1. 将一个账户的内容持续减少指定的数量。
  2. 不断增加对方账户的内容到指定的金额。
  3. 如果上面的一个成功,另一个也必须成功。

You can achieve 1. and 2. by using Atomics, but you will have to use something other that doubleas there is no AtomicDouble. AtomicLongwould probably be your best bet.

您可以通过使用Atomics实现 1. 和 2. ,但您必须使用其他东西,double因为没有AtomicDouble. AtomicLong可能是你最好的选择。

So you're left with your third requirement - if one succeeds the other mustsucceed. There is a simple technique that works superbly with atomics and that is using the getAndAddmethods.

所以你剩下第三个要求——如果一个成功,另一个必须成功。有一种简单的技术可以很好地与原子一起使用,那就是使用这些getAndAdd方法。

class Account {
  AtomicLong balance = new AtomicLong ();
}

...
Long oldDebtor = null;
Long oldCreditor = null;
try {
  // Increase one.
  oldDebtor = debtor.balance.getAndAdd(value);
  // Decrease the other.
  oldCreditor = creditor.balance.gtAndAdd(-value);
} catch (Exception e) {
  // Most likely (but still incredibly unlikely) InterruptedException but theoretically anything.
  // Roll back
  if ( oldDebtor != null ) {
    debtor.getAndAdd(-value);
  }
  if ( oldCreditor != null ) {
    creditor.getAndAdd(value);
  }
  // Re-throw after cleanup.
  throw (e);
}