java - 使用 java.util.concurrent.locks.Lock 而不是同步的 : can my code avoid dead-lock in a bank transfer scenario?

标签 java multithreading concurrency locking deadlock

我正在研究避免死锁的措施,其中一种可能的方法是通过强制线程放弃它在访问另一个锁但无法访问该锁时已经持有的锁来打破循环等待。
以最简单的银行账户转账为例:


class Account {
  private int balance;
 
  void transfer(Account target, int amt){
    //lock the from account
    synchronized(this) {              
      //lock the to account
      synchronized(target) {           
        if (this.balance > amt) {
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}
使用嵌套同步块(synchronized block)时,当一个线程持有当前账户的监视器时,当它无法访问目标账户的锁时,它会被阻塞,直到锁可用,并且无法响应任何中断。它没有机会先尝试第二个锁是否可用,这样就没有机会放弃它已经持有的锁。
而Lock接口(interface)提供tryLock()可以无缝支持场景的方法
.它还提供lockInterruptibly()这意味着当它由于无法访问第二个锁而被阻塞时,它仍然可以响应中断。但我想知道的是,当被中断时,是什么确保线程会丢弃它已经拥有的所有锁?因为我没有找到像 Object.wait() 这样的文档其中有文档明确表示它将放弃它所拥有的所有同步。除了 Thread.interrupt()只是调用一个我看不到任何实现的 native 方法。
好吧,我本能地理解必须释放锁,否则在线程被中断后没有其他线程将能够访问锁,但我只想知道我自己到底发生了什么。

更新:
正如@Holger 指出的那样,我在理解Object.wait() 时犯了一个错误。在无法访问监视器时执行此操作。我已经尝试为这种情况制定一个锁定解决方案,但我不确定它是否有一些谬误。如果代码中有错误,希望有人能指出。
而且我还想知道我的代码是否可以处理可能提出的场景。
下面是代码:
    class Account {
        private int balance;
        Lock lock = new ReentrantLock();

        void transfer(Account target, int amt) {
            //lock the from account
            try {
                if (lock.tryLock(50, TimeUnit.MILLISECONDS)) {
                    if (target.lock.tryLock(50, TimeUnit.MILLISECONDS)) {
                        this.balance -= amt;
                        target.balance += amt;
                        target.lock.unlock();
                    }
                    lock.unlock();
                }

            } catch (InterruptedException e) {
                System.out.println("Interrupted while attempting the lock.");
            }
        }
    }
如果有人可以使用 lockInterruptibly() 提供有关如何处理这种情况的示例,我将不胜感激。 .

最佳答案

您使用 tryLock 的方法正朝着无死锁解决方案的方向发展。但是您必须确保在特殊情况下正确关闭锁。此外,由于 tryLock可能会失败,导致 Action 没有被执行,你需要返回一个状态:

class Account {
    private int balance;
    Lock lock = new ReentrantLock();

    boolean transfer(Account target, int amt) {
        boolean success = false;
        //lock the from account
        try {
            if(lock.tryLock(50, TimeUnit.MILLISECONDS)) try {
                if(target.lock.tryLock(50, TimeUnit.MILLISECONDS)) try {
                    this.balance -= amt;
                    target.balance += amt;
                    success = true;
                }
                finally {
                    target.lock.unlock();
                }
            }
            finally {
                lock.unlock();
            }
        } catch(InterruptedException ex) {
            // success still false
        }
        return success;
    }
}
然后,您必须考虑如何处理失败,重试操作或放弃。这导致了另一个问题,即所谓的活锁问题。当一个线程尝试从 A 传输到 B 而另一个线程尝试从 B 传输到 A 时,两者都可能成功锁定其源,然后又无法锁定另一个。重复尝试可能会反复导致失败。涉及更多线程的类似场景是可能的。在这些情况下,不会发生死锁,但线程仍然无法取得进展。
该解决方案类似于另一种避免死锁的方法:始终以预定义的顺序获取锁。
class Account {
    final BigInteger accountNumber;
    private int balance;
    Lock lock = new ReentrantLock();

    Account(BigInteger accountNumber) {
        this.accountNumber = accountNumber;
    }

    void transfer(Account target, int amt) {
        if(accountNumber.compareTo(target.accountNumber) < 0)
            transfer(this, target, amt);
        else
            transfer(target, this, -amt);
    }

    static void transfer(Account from, Account to, int amt) {
        from.lock.lock();
        try {
            to.lock.lock();
            try {
                from.balance -= amt;
                to.balance += amt;
            }
            finally {
                to.lock.unlock();
            }
        }
        finally {
            from.lock.unlock();
        }
    }
}
通过总是先锁定具有较小数字的帐户,我们永远不会遇到拥有较大数字锁的另一个线程试图获取我们已经拥有的锁的情况。

关于java - 使用 java.util.concurrent.locks.Lock 而不是同步的 : can my code avoid dead-lock in a bank transfer scenario?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62668648/

相关文章:

java - 在单例类中并发调用方法

c++ - 使用 memory_order_relaxed 进行存储,使用 memory_order_acquire 进行加载

javascript - Java:使用 JavaScript 上传图像 - 文件损坏、损坏或太大

java - 数一下没有。使用给定的输入字符串可以创建单词的次数

multithreading - Thread.FreeOnTerminate := True, 内存泄漏和幽灵运行

c++ - 锁定文件并避免同一进程访问它两次

c#无法停止windows服务

java - 按索引查找 token 进行括号匹配

java - 如何让java的随机生成器在构造函数语句中生成 double for变量?

java - Initialize-On-Demand 习语与单例实现中的简单静态初始化程序