21

次の例でデッドロックを回避するための代替方法は何でしょうか。次の例は、典型的な銀行口座の転送デッドロックの問題です。実際にそれを解決するためのより良いアプローチのいくつかは何ですか?

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

次のように転送方法でネストされたロックアウトを分離すると、デッドロックの問題が解決するかどうか疑問に思っています。

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

6 に答える 6

35

アカウントを並べ替えます。デッドロックは、アカウントの順序(a、bと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);
           }
      }
 }
于 2012-11-10T23:06:23.060 に答える
10

注文されたロックの解決策に加えて、アカウントの転送を実行する前にプライベート静的最終ロックオブジェクトで同期することにより、デッドロックを回避することもできます。

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

このソリューションには、プライベート静的ロックがシステムを「順次」転送の実行に制限するという問題があります。

もう1つは、各アカウントに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);
        }

 }

これらのロックが無期限に保持されることはないため、このアプローチではデッドロックは発生しません。現在のオブジェクトのロックが取得されたが、2番目のロックが使用できない場合、最初のロックが解放され、スレッドはロックを再取得しようとする前に、指定された時間スリープします。

于 2012-11-10T23:32:18.300 に答える
9

これは古典的な質問です。私は2つの可能な解決策を見ます:

  1. アカウントを並べ替えて、IDが別のアカウントよりも小さいアカウントで同期します。この方法は、第10章の並行性Java並行性の実践のバイブルで言及されています。この本では、作成者はシステムハッシュコードを使用してアカウントを区別します。java.lang.System#identityHashCodeを参照してください。
  2. 2番目の解決策はあなたによって言及されています-はい、ネストされた同期ブロックを回避することができ、コードがデッドロックにつながることはありません。ただし、その場合、最初のアカウントからお金を引き出すと、2番目のアカウントがかなりの時間ロックされ、おそらく最初のアカウントにお金を戻す必要があるため、処理に問題が発生する可能性があります。これは良くありません。ネストされた同期と2つのアカウントのロックの方が優れており、より一般的に使用されるソリューションであるためです。
于 2012-11-11T00:10:19.253 に答える
5

アカウントごとに(アカウントクラスで)個別のロックを作成し、トランザクションを実行する前に両方のロックを取得することもできます。見てください:

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

2つのロックを取得した後、安全性を心配することなく転送を行うことができます。

    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);
        }
    }
于 2014-10-07T17:26:55.777 に答える
0

これが述べられた問題の解決策です。

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

}
于 2018-01-16T17:04:36.527 に答える
-1

満たす必要のある3つの要件があります。

  1. 1つのアカウントの内容を指定された量だけ一貫して減らします。
  2. 他のアカウントの内容を指定された量だけ一貫して増やします。
  3. 上記のいずれかが成功した場合、もう一方も成功する必要があります。

Atomicsを使用して1.と2.を達成できますが、がないため、それ以外のものを使用する必要がdoubleありAtomicDoubleます。AtomicLongおそらくあなたの最善の策でしょう。

したがって、3番目の要件が残ります。一方が成功した場合、もう一方も成功する必要があります。アトミックで見事に機能し、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);
}
于 2012-11-11T00:35:32.390 に答える