死锁的四个必要条件_研究死锁–第5部分:使用显式锁定

死锁的四个必要条件

死锁的四个必要条件

在我的上一个博客中,我研究了使用Java的传统synchronized关键字和锁排序来修复破碎的,死锁的余额转移示例代码。 但是,存在另一种方法,称为显式锁定。

这里,将锁定机制称为显式而非隐式的想法是,显式表示它不是Java语言的一部分,并且已编写了一些类来实现锁定功能。 另一方面,隐式锁定可以定义为该语言的一部分,并且可以使用语言关键字synchronchized在后台实现锁定。

您可能会争论显式锁定是否是一个好主意。 是否应该对Java语言进行改进,使其包括显式锁定功能,而不是向已经庞大的API中添加另一组类? 例如: trysynchronized()

显式锁定基于Lock接口及其ReentrantLock实现。 与传统的synchronized关键字相比, Lock包含许多方法,这些方法可以使您对锁定进行更多控制。 它具有您期望的方法,例如lock()会在代码的受保护部分中创建一个入口点,而unlock()会在其中创建出口点。 它还具有tryLock()tryLock(long time,TimeUnit unit) ,只有在有可用但尚未被另一个线程获取的情况下才会获取锁,而tryLock(long time,TimeUnit unit)则将尝试获取锁定,如果不可用则等待指定的计时器在放弃之前过期。

为了实现显式锁定,我首先向本系列以前的博客中使用的Account类添加了Lock接口。

public class Account implements Lock {

  private final int number;

  private int balance;

  private final ReentrantLock lock;

  public Account(int number, int openingBalance) {
    this.number = number;
    this.balance = openingBalance;
    this.lock = new ReentrantLock();
  }

  public void withDrawAmount(int amount) throws OverdrawnException {

    if (amount > balance) {
      throw new OverdrawnException();
    }

    balance -= amount;
  }

  public void deposit(int amount) {

    balance += amount;
  }

  public int getNumber() {
    return number;
  }

  public int getBalance() {
    return balance;
  }

  // ------- Lock interface implementation

  @Override
  public void lock() {
    lock.lock();
  }

  @Override
  public void lockInterruptibly() throws InterruptedException {
    lock.lockInterruptibly();
  }

  @Override
  public Condition newCondition() {
    return lock.newCondition();
  }

  @Override
  public boolean tryLock() {
    return lock.tryLock();
  }

  @Override
  public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
    return lock.tryLock(arg0, arg1);
  }

  @Override
  public void unlock() {
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

}

在上面的代码中,您可以看到我通过封装一个ReentrantLock对象来支持聚合, Account类将锁定功能委托给该对象。 唯一需要注意的小型GOTCHA是在unlock()实现中:

@Override
  public void unlock() {
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

它有一个附加的if()语句,该语句检查调用线程是否是当前持有锁的线程。 如果错过了这一行代码,那么您将获得以下IllegalMonitorStateException

Exception in thread 'Thread-7' java.lang.IllegalMonitorStateException
 at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
 at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
 at threads.lock.Account.unlock(Account.java:76)
 at threads.lock.TrylockDemo$BadTransferOperation.transfer(TrylockDemo.java:98)
 at threads.lock.TrylockDemo$BadTransferOperation.run(TrylockDemo.java:67)

那么,这是如何实现的呢? 以下是基于我的原始DeadLockDemo程序的TryLockDemo示例的列表。

public class TrylockDemo {

  private static final int NUM_ACCOUNTS = 10;
  private static final int NUM_THREADS = 20;
  private static final int NUM_ITERATIONS = 100000;
  private static final int LOCK_ATTEMPTS = 10000;

  static final Random rnd = new Random();

  List<Account> accounts = new ArrayList<Account>();

  public static void main(String args[]) {

    TrylockDemo demo = new TrylockDemo();
    demo.setUp();
    demo.run();
  }

  void setUp() {

    for (int i = 0; i < NUM_ACCOUNTS; i++) {
      Account account = new Account(i, 1000);
      accounts.add(account);
    }
  }

  void run() {

    for (int i = 0; i < NUM_THREADS; i++) {
      new BadTransferOperation(i).start();
    }
  }

  class BadTransferOperation extends Thread {

    int threadNum;

    BadTransferOperation(int threadNum) {
      this.threadNum = threadNum;
    }

    @Override
    public void run() {

      int transactionCount = 0;

      for (int i = 0; i < NUM_ITERATIONS; i++) {

        Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));
        Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));
        int amount = rnd.nextInt(1000);

        if (!toAccount.equals(fromAccount)) {

          boolean successfulTransfer = false;

          try {
            successfulTransfer = transfer(fromAccount, toAccount, amount);

          } catch (OverdrawnException e) {
            successfulTransfer = true;
          }

          if (successfulTransfer) {
            transactionCount++;
          }

        }
      }

      System.out.println("Thread Complete: " + threadNum + " Successfully made " + transactionCount + " out of "
          + NUM_ITERATIONS);
    }

    private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {

      boolean success = false;
      for (int i = 0; i < LOCK_ATTEMPTS; i++) {

        try {
          if (fromAccount.tryLock()) {
            try {
              if (toAccount.tryLock()) {

                success = true;
                fromAccount.withDrawAmount(transferAmount);
                toAccount.deposit(transferAmount);
                break;
              }
            } finally {
              toAccount.unlock();
            }
          }
        } finally {
          fromAccount.unlock();
        }
      }

      return success;
    }

  }
}

想法是一样的,我有一个银行帐户列表,我将随机选择两个帐户,然后从一个帐户中随机转移一个金额。 问题的核心是我更新的transfer(...)方法,如下所示。

private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {

      boolean success = false;
      for (int i = 0; i < LOCK_ATTEMPTS; i++) {

        try {
          if (fromAccount.tryLock()) {
            try {
              if (toAccount.tryLock()) {

                success = true;
                fromAccount.withDrawAmount(transferAmount);
                toAccount.deposit(transferAmount);
                break;
              }
            } finally {
              toAccount.unlock();
            }
          }
        } finally {
          fromAccount.unlock();
        }
      }

      return success;
    }

这里的想法是我尝试锁定fromAccount然后锁定toAccount 。 如果可行,那么我先进行转移,然后再记得解锁两个帐户。 如果那时帐户已经被锁定,那么我的tryLock()方法将失败,整个过程将循环并再次尝试。 尝试10000次锁定后,线程放弃并忽略传输。 我猜想在现实世界的应用程序中,您希望将此故障放入某种队列中,以便以后进行调查。

在使用显式锁定时,您必须考虑它的工作原理,因此请看下面的结果…

Thread Complete: 17 Successfully made 58142 out of 100000
Thread Complete: 12 Successfully made 57627 out of 100000
Thread Complete: 9 Successfully made 57901 out of 100000
Thread Complete: 16 Successfully made 56754 out of 100000
Thread Complete: 3 Successfully made 56914 out of 100000
Thread Complete: 14 Successfully made 57048 out of 100000
Thread Complete: 8 Successfully made 56817 out of 100000
Thread Complete: 4 Successfully made 57134 out of 100000
Thread Complete: 15 Successfully made 56636 out of 100000
Thread Complete: 19 Successfully made 56399 out of 100000
Thread Complete: 2 Successfully made 56603 out of 100000
Thread Complete: 13 Successfully made 56889 out of 100000
Thread Complete: 0 Successfully made 56904 out of 100000
Thread Complete: 5 Successfully made 57119 out of 100000
Thread Complete: 7 Successfully made 56776 out of 100000
Thread Complete: 6 Successfully made 57076 out of 100000
Thread Complete: 10 Successfully made 56871 out of 100000
Thread Complete: 11 Successfully made 56863 out of 100000
Thread Complete: 18 Successfully made 56916 out of 100000
Thread Complete: 1 Successfully made 57304 out of 100000

这些表明,尽管该程序没有死锁并无限期地挂起,但它仅设法使余额转移只超过转移请求的一半。 这意味着它正在消耗大量的处理能力,包括循环,循环和循环-总体上不是很有效。 另外,我刚才说过该程序“没有死锁并无限期地挂起”,这不是真的。 如果您考虑正在发生的事情,那么您将意识到程序陷入僵局,然后退出这种情况。

我的显式锁定演示代码的第二个版本使用上面提到的tryLock(long time,TimeUnit unit)

private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {

      boolean success = false;

      try {
        if (fromAccount.tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) {
          try {
            if (toAccount.tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) {

              success = true;
              fromAccount.withDrawAmount(transferAmount);
              toAccount.deposit(transferAmount);
            }
          } finally {
            toAccount.unlock();
          }
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      } finally {
        fromAccount.unlock();
      }

      return success;
    }

在这段代码中,我用1毫秒的tryLock(...)超时替换了for循环。 这意味着,当tryLock(...)被调用并且无法获取锁时,它将等待1 ms,然后回滚并放弃。

Thread Complete: 0 Successfully made 26637 out of 100000
Thread Complete: 14 Successfully made 26516 out of 100000
Thread Complete: 3 Successfully made 26552 out of 100000
Thread Complete: 11 Successfully made 26653 out of 100000
Thread Complete: 7 Successfully made 26399 out of 100000
Thread Complete: 1 Successfully made 26602 out of 100000
Thread Complete: 18 Successfully made 26606 out of 100000
Thread Complete: 17 Successfully made 26358 out of 100000
Thread Complete: 19 Successfully made 26407 out of 100000
Thread Complete: 16 Successfully made 26312 out of 100000
Thread Complete: 15 Successfully made 26449 out of 100000
Thread Complete: 5 Successfully made 26388 out of 100000
Thread Complete: 8 Successfully made 26613 out of 100000
Thread Complete: 2 Successfully made 26504 out of 100000
Thread Complete: 6 Successfully made 26420 out of 100000
Thread Complete: 4 Successfully made 26452 out of 100000
Thread Complete: 9 Successfully made 26287 out of 100000
Thread Complete: 12 Successfully made 26507 out of 100000
Thread Complete: 10 Successfully made 26660 out of 100000
Thread Complete: 13 Successfully made 26523 out of 100000

上面的结果表明,使用计时器时,余额转移成功率甚至下降到25%以上。 尽管现在还没有消耗大量的处理器时间,但效率仍然很低。

在相当长的一段时间内,我可能会花时间处理这两个代码示例,从而选择可以调整应用程序并提高性能的变量,但是最终,没有什么真正的选择可以正确地进行锁排序。 我个人更愿意在可能的情况下使用老式的synchronized关键字隐式锁定,并为死锁代码过时,陈旧,难以理解的少数情况保留显式锁定,我已经尝试了其他所有方法,该应用需要上线,已经很晚了,该回家了……

有关更多信息,请参阅本系列中的其他博客

该系列以及其他博客的所有源代码都可以在Github上找到,网址为git://github.com/roghughe/captaindebug.git

参考:调查死锁–第5部分:使用来自Captain Debug博客博客的JCG合作伙伴Roger Hughes的显式锁定

翻译自: https://www.javacodegeeks.com/2012/11/investigating-deadlocks-part-5-using-explicit-locking.html

死锁的四个必要条件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值