由于不当的执行顺序导致的死锁

简介: 由于不当的执行顺序导致的死锁

为了保证线程的安全,我们引入了加锁机制,但是如果不加限制的使用加锁,就有可能会导致顺序死锁(Lock-Ordering Deadlock)。上篇文章我们也提到了在线程词中因为资源的不足而导致的资源死锁(Resource Deadlock)。


顺序死锁的问题


我们来讨论一个经常存在的账户转账的问题。账户A要转账给账户B。为了保证在转账的过程中A和B不被其他的线程意外的操作,我们需要给A和B加锁,然后再进行转账操作, 我们看下转账的代码:


public void transferMoneyDeadLock(Account from,Account to, int amount) throws InsufficientAmountException {
        synchronized (from){
            synchronized (to){
                transfer(from,to,amount);
            }
        }
    }
    private void transfer(Account from,Account to, int amount) throws InsufficientAmountException {
        if(from.getBalance() < amount){
            throw new InsufficientAmountException();
        }else{
            from.debit(amount);
            to.credit(amount);
        }
    }


看起来上面的程序好像没有问题,因为我们给from和to都加了锁,程序应该可以很完美的按照我们的要求来执行。


那如果我们考虑下面的一个场景:


A:transferMoneyDeadLock(accountA, accountB, 20)
B:transferMoneyDeadLock(accountB, accountA, 10)


如果A和B同时执行,则可能会产生A获得了accountA的锁,而B获得了accountB的锁。从而后面的代码无法继续执行,从而导致了死锁。


对于这样的情况,我们有没有什么好办法来处理呢?


加入不管参数怎么传递,我们都先lock accountA再lock accountB是不是就不会出现死锁的问题了呢?


我们看下代码实现:


private void transfer(Account from,Account to, int amount) throws InsufficientAmountException {
        if(from.getBalance() < amount){
            throw new InsufficientAmountException();
        }else{
            from.debit(amount);
            to.credit(amount);
        }
    }
    public void transferMoney(Account from,Account to, int amount) throws InsufficientAmountException {
       int fromHash= System.identityHashCode(from);
       int toHash = System.identityHashCode(to);
       if(fromHash < toHash){
           synchronized (from){
               synchronized (to){
                   transfer(from,to, amount);
               }
           }
       }else if(fromHash < toHash){
            synchronized (to){
                synchronized (from){
                    transfer(from,to, amount);
                }
            }
        }else{
           synchronized (lock){
           synchronized (from) {
               synchronized (to) {
                   transfer(from, to, amount);
               }
             }
           }
       }
    }


上面的例子中,我们使用了System.identityHashCode来获得两个账号的hash值,通过比较hash值的大小来选定lock的顺序。


如果两个账号的hash值恰好相等的情况下,我们引入了一个新的外部lock,从而保证同一时间只有一个线程能够运行内部的方法,从而保证了任务的执行而不产生死锁。


本文的例子可以参考https://github.com/ddean2009/learn-java-concurrency/tree/master/accountTransferLock

相关文章
|
6月前
|
安全 开发工具
防止死锁的关键策略
防止死锁的关键策略包括:避免持有多个锁,按相同顺序获取,设置锁获取超时,减小锁粒度,以及利用死锁检测工具。确保线程安全,减少锁竞争,可提高系统并发性能。
70 1
多线程-同步代码块中的隐患及解决办法
多线程-同步代码块中的隐患及解决办法
53 0
|
6天前
|
算法
死锁的必要条件
互斥条件:同一资源同时只能由一个线程读取 不可抢占条件:不能强行剥夺线程占有的资源 请求和保持条件:请求其他资源的同时对自己手中的资源保持不放 循环等待条件:在相互等待资源的过程中,形成一个闭环 想要预防死锁,只需要破坏其中一个条件即可,银行家算法可以预防死锁
|
3月前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
6月前
|
监控 IDE 测试技术
预防和处理线程死循环的关键步骤
【5月更文挑战第24天】预防和处理线程死循环的关键步骤包括理解死循环成因(逻辑错误、竞争条件、资源泄漏)、编码阶段采取预防措施(明确退出条件、避免无限递归、正确使用锁、资源管理、健壮的错误处理)、调试定位(断点、日志、线程分析工具、性能分析)、解决问题(修改代码、临时解决方案、逐步排查)以及测试验证(充分测试、专用测试用例)。遵循这些步骤可有效管理线程死循环风险。
119 1
|
6月前
|
算法 Java 编译器
【JavaEE多线程】掌握锁策略与预防死锁
【JavaEE多线程】掌握锁策略与预防死锁
61 2
什么是死锁?产生死锁的原因?产生死锁的四个必要条件?死锁的避免与预防?
什么是死锁?产生死锁的原因?产生死锁的四个必要条件?死锁的避免与预防?
274 0
|
6月前
|
Go
并发陷阱:死锁、活锁和饥饿
并发陷阱:死锁、活锁和饥饿
109 0
|
6月前
|
算法 安全
解决死锁的方法
解决死锁的方法
什么条件下会产出死锁,如何避免死锁?
一个去美团面试的小伙伴私我说,被面试官问到一个死锁的问题难道了,面试前还特意刷了题,面试的时候就是脑子一片空白不知道怎么回答。今天,我给大家彻底讲明白。
94 1
什么条件下会产出死锁,如何避免死锁?