使用java.util.concurrent.locks.Lock代替Synchronized:我的代码在银行转账场景中可以避免死锁吗?

使用java.util.concurrent.locks.Lock代替Synchronized:我的代码在银行转账场景中可以避免死锁吗?,java,multithreading,concurrency,locking,deadlock,Java,Multithreading,Concurrency,Locking,Deadlock,我正在研究避免死锁的措施,其中一种可能的方法是,当线程正在访问另一个锁但该锁不可访问时,通过强制线程放弃它已经持有的锁来打破循环等待 以最简单的银行账户转账为例: 类别帐户{ 私人国际收支平衡; 无效转账(科目目标,整数金额){ //锁定来自帐户的帐户 已同步(此){ //锁定帐户 已同步(目标){ 如果(此项余额>金额){ 此余额-=金额; 目标余额+=金额; } } } } } 当使用嵌套的同步块时,当线程持有当前帐户的监视器时,当它无法访问目标帐户的锁时,它将被阻止,直到锁可用,并且

我正在研究避免死锁的措施,其中一种可能的方法是,当线程正在访问另一个锁但该锁不可访问时,通过强制线程放弃它已经持有的锁来打破循环等待

以最简单的银行账户转账为例:


类别帐户{
私人国际收支平衡;
无效转账(科目目标,整数金额){
//锁定来自帐户的帐户
已同步(此){
//锁定帐户
已同步(目标){
如果(此项余额>金额){
此余额-=金额;
目标余额+=金额;
}
}
}
} 
}
当使用嵌套的同步块时,当线程持有当前帐户的监视器时,当它无法访问目标帐户的锁时,它将被阻止,直到锁可用,并且将无法响应任何中断。它将没有机会先尝试第二个锁是否可用,这样就没有机会放弃它已经拥有的锁。 而Lock接口提供了
tryLock()
方法,可以无缝支持场景 . 它还提供了
lockInterruptibly()
,这意味着当它由于无法访问第二个锁而被阻塞时,它仍然可以响应中断。但我想知道的是,当被中断时,是什么确保线程将扔掉它已经持有的所有锁?因为我在任何地方都找不到像
Object.wait()
这样的文档,其中的文档明确表示它将放弃它所持有的所有同步。此外,
Thread.interrupt()
只是调用一个本机方法,我看不到任何实现。 我本能地理解锁必须被释放,否则在线程被中断后,其他线程将无法访问锁,但我只想知道我自己到底发生了什么


更新: 正如@Holger所指出的,我在理解Object.wait()在无法访问监视器时所做的事情时犯了一个错误。我已经尝试为这个场景制作了一个锁解决方案,但我不确定它是否有错误。如果代码中有错误,希望有人能指出。 我还想知道我的代码是否能够处理可能提出的场景。 代码如下:

类帐户{
私人国际收支平衡;
Lock Lock=新的可重入锁();
无效转账(科目目标,整数金额){
//锁定来自帐户的帐户
试一试{
if(lock.tryLock(50,时间单位毫秒)){
if(target.lock.tryLock(50,TimeUnit.ms)){
此余额-=金额;
目标余额+=金额;
target.lock.unlock();
}
lock.unlock();
}
}捕捉(中断异常e){
System.out.println(“尝试锁定时中断”);
}
}
}

另外,如果有人能给出一个关于如何使用
lockinterruptbly()

处理这种情况的示例,我将非常感激。您使用
tryLock
的方法正朝着无死锁解决方案的方向发展。但在特殊情况下,您必须确保锁正确关闭。此外,由于
tryLock
可能会失败,导致操作无法执行,因此您需要返回一个状态:

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();
        }
    }
}
类帐户{
最终大整数帐号;
私人国际收支平衡;
Lock Lock=新的可重入锁();
帐户(BigInteger accountNumber){
this.accountNumber=accountNumber;
}
无效转账(科目目标,整数金额){
if(accountNumber.compareTo(target.accountNumber)<0)
转账(本次、目标、金额);
其他的
转账(目标,本金额,-amt);
}
静态作废转账(账户来源、账户目的、整数金额){
from.lock.lock();
试一试{
to.lock.lock();
试一试{
from.balance-=金额;
to.balance+=金额;
}
最后{
to.lock.unlock();
}
}
最后{
from.lock.unlock();
}
}
}

通过总是先用较小的数字锁定帐户,我们永远不会遇到另一个拥有较大数字锁的线程试图获取我们已经拥有的锁的情况。

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

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时,两个线程都可能成功地锁定了它们的源,然后锁定另一个线程失败。反复尝试可能会导致失败