2012-11-10 119 views
16

我想知道在下面的示例中避免死鎖的替代方法是什麼。以下示例是一個典型的銀行帳戶傳輸死鎖問題。在實踐中解決它有哪些更好的方法?避免死鎖示例

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

你的例子不是經典的死鎖例子。它總是鎖定同一個線程的帳戶,所以不會發生死鎖。是否在傳輸方法中嘗試使用此代碼:靜態無效傳輸(BankAccount from,BankAccount to,double amount){ synchronized(from){ \t from.withdraw(amount); synchronized(to){ to.deposit(amount); } } } – supernova

回答

23

排序的賬戶。死鎖來自帳戶的排序(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); 
      } 
     } 
} 
+3

這將在處理兩個以上的帳戶時正常工作,對嗎? – peter

+0

我可能不太瞭解這個概念,但是這兩個賬戶的平衡狀況如何呢?據我瞭解,它們不會被交換,因此死鎖仍然存在。 –

+3

@Piotr:不,在這種情況下,您需要根據他們獨有的東西(例如帳號或數據庫中的主鍵等)對帳戶進行排序。只要它是所有參與者的穩定排序(即沒有重複,如您所建議的),實際排序並不重要。 –

6

除了鎖定的解決方案訂購還可以通過執行任何轉賬前的私有靜態最終鎖定對象上同步避免死鎖。

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

此解決方案存在的問題是私有靜態鎖限制系統執行「順序」傳輸。

另外一個可能是,如果每個賬戶有一個的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); 
     } 

} 

死鎖,因爲鎖是從來無限期持有不發生在這個方法做的。如果獲取當前對象的鎖定,但第二個鎖定不可用,則第一個鎖定將被釋放,並且線程會在嘗試重新獲取鎖定之前休眠指定的時間量。

+0

我在面試的時候發明了這個解決方案的時候並不知道正確的答案) – BrownFurSeal

+0

第一種情況下,你的意思是「順序」嗎? – peter

+0

一個線程持有鎖正在計算,另一個正在等待。第一個完成工作後,另一個將完成工作。兩者都沒有並行工作。 – dreamcrash

8

這是一個經典問題。我看到兩種可能的解決方案:

  1. 對帳戶進行排序並在id低於另一個帳戶時進行同步。 這個方法在第10章的併發性Java併發實踐聖經中提到。在本書中,作者使用系統哈希代碼來區分帳戶。見java.lang.System#identityHashCode
  2. 第二種解決方案是你提到的 - 是的,你可以避免嵌套同步塊,你的代碼不會導致死鎖。但在這種情況下,處理可能會遇到一些問題,因爲如果您從第一個帳戶提款,第二個帳戶可能會在任何重要時間被鎖定,並且可能需要將錢還給第一個帳戶。這並不好,因爲嵌套同步和兩個帳戶的鎖是更好和更常用的解決方案。
-1

有三個要求必須滿足:

  1. 始終按照指定的量減少一個帳戶的內容。
  2. 以指定的量持續增加另一個賬戶的內容。
  3. 如果其中一個成功,另一個也必須成功。

您可以通過使用Atomics達到1和2,但你將不得不使用其他的是double的東西,因爲沒有AtomicDoubleAtomicLong可能是你最好的選擇。

所以你只剩下你的第三個要求 - 如果一個成功,另一個必須成功。有一種簡單的技術可以很好地與原子技術配合使用,並且使用了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); 
} 
+0

Atomics不會拋出中斷的異常。 – BrownFurSeal

+0

正確!但我敢打賭,當OP得到解決時,Account.credit/debit會拋出一些東西。 – OldCurmudgeon

3

您還可以爲每個帳戶(在帳戶類中)創建單獨的鎖,然後在執行事務之前獲取這兩個鎖。看看:

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

得到兩個鎖後,你可以做轉移,而不必擔心安全。

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

}