避免死锁示例

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

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

对帐户进行排序。 死锁来自账户的排序(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); } } } 

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

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

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

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

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

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

获得两把锁后,你可以做转移而不用担心安全。

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

这是所述问题的解决方案。

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

您必须满足三个要求:

  1. 始终按指定的金额减少一个帐户的内容。
  2. 始终按指定金额增加其他帐户的内容。
  3. 如果其中一个成功,另一个也必须成功。

您可以使用Atomics实现1.和2.但是由于没有AtomicDouble ,您将不得不使用其他double AtomicDoubleAtomicLong可能是你最好的选择。

所以你留下了你的第三个要求 – 如果一个成功,另一个必须成功。 有一种简单的技术可以很好地处理primefaces,并使用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); }