侧边栏壁纸
博主头像
博客技术 博主等级

行动起来,活在当下

  • 累计撰写 42 篇文章
  • 累计创建 4 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

如何定位避免死锁

Administrator
2025-03-03 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

1.什么是死锁

2.如何解决死锁

死锁总结:并发下线程因为相互等待对方资源 导致永久 阻塞的现象

一、转账引发的血案:当两个账户互相较劲

让我们通过一个真实的转账场景,看看死锁是怎么悄悄发生的:

// 张三的银行账户
class Account {
    private int balance;
    
    void transfer(Account target, int amount) {
        synchronized(this) {       // 先锁住自己
            Thread.sleep(2000);  // 模拟耗时操作
            synchronized(target) { // 再锁对方
                this.balance -= amount;
                target.balance += amount;
            }
        }
    }
}

// 两个线程同时转账
new Thread(() -> zhangsan.transfer(lisi, 50)).start();
new Thread(() -> lisi.transfer(zhangsan, 50)).start();

当张三和李四同时给对方转账时,就像两个人在狭窄的走廊相遇——张三拿着自己的锁想要李四的锁,李四攥着自己的锁等着张三的锁。结果就是两个线程永远卡在synchronized代码块里大眼瞪小眼。

二、线上死锁急救包:三步定位问题

当监控报警响起,按照这个流程快速响应:

  1. 查心跳:用jps -l快速找到卡死的Java进程ID

  2. 拍快照:执行jstack 进程ID > thread_dump.log导出线程快照

  3. 看诊断:在日志中搜索"deadlock",你会看到类似这样的关键信息:

Found one Java-level deadlock:
"Thread-1":
  waiting to lock monitor 0x00007f3a48005e00 (object 0x000000076b7d9c78, a Account),
  which is held by "Thread-0"

"Thread-0":
  waiting to lock monitor 0x00007f3a48006c00 (object 0x000000076b7d9c98, a Account),
  which is held by "Thread-1"

这段诊断报告就像医生的X光片,清晰显示出两个线程如何互相掐住对方的脖子。根据提示的类名和行号,能快速定位到问题代码。

三、根治死锁四板斧:从根源解决问题

第一招:破除资源独占(互斥条件)

  • 改用原子操作:对于简单数值操作,用AtomicInteger代替同步锁

private AtomicInteger balance = new AtomicInteger();

void safeTransfer(Account target, int amount) {
    balance.getAndAdd(-amount);
    target.balance.getAndAdd(amount);
}

第二招:强制资源打包(占有且等待)

  • 银行金库法:建立全局资源管理器,一次性申请所有需要的资源

class ResourceManager {
    private Set<Account> lockedAccounts = new HashSet<>();
    
    synchronized boolean lockAccounts(Account... accounts) {
        if(Collections.disjoint(lockedAccounts, Arrays.asList(accounts))) {
            lockedAccounts.addAll(Arrays.asList(accounts));
            return true;
        }
        return false;
    }
    
    synchronized void unlockAccounts(Account... accounts) {
        lockedAccounts.removeAll(Arrays.asList(accounts));
    }
}

第三招:设置等待时限(不可抢占)

  • 给爱情加个期限:用ReentrantLock代替synchronized,设置抢锁超时

private Lock lock = new ReentrantLock();

void tryTransfer(Account target, int amount) {
    try {
        if(lock.tryLock(2, TimeUnit.SECONDS)) {
            if(target.lock.tryLock(2, TimeUnit.SECONDS)) {
                // 转账操作
            }
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        target.lock.unlock();
        lock.unlock();
    }
}

第四招:统一资源顺序(循环等待)

  • 学食堂排队:按照账户ID顺序加锁

void orderedTransfer(Account target, int amount) {
    Account first = this.id < target.id ? this : target;
    Account second = this.id < target.id ? target : this;
    
    synchronized(first) {
        synchronized(second) {
            // 转账操作
        }
    }
}

四、防死锁的智慧:取舍的艺术

在实际开发中,安全与性能就像天平的两端。

下次当你设计转账功能时,不妨多问自己几个问题:

  1. 真的需要同时锁住两个账户吗?

  2. 能否改用版本号乐观锁?

  3. 事务超时时间设置合理吗?

  4. 有没有考虑网络抖动导致锁滞留?

0

评论区