You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ReentrantLock实现用户级锁遇死锁问题求助

问题排查与解决方案

首先,咱们来拆解下你代码里导致死锁和永久等待的核心问题:

核心死锁原因

你的AccountLock实现里,lock和unlock操作在synchronizedMap的锁保护下执行了阻塞的lock()/unlock()调用,这直接引发了死锁场景:

  1. 线程A(处理用户1234)成功获取了该用户的ReentrantLock,进入sleep阶段;此时它后续需要调用unlock(),而unlock()里的computeIfPresent必须先拿到synchronizedMap的锁。
  2. 线程B(同样处理用户1234)调用lock(),先执行computeIfPresent——这个方法会先获取synchronizedMap的锁,然后尝试调用lock.lock();但锁已经被线程A持有,线程B就卡在这一步,同时一直持有synchronizedMap的锁不放。
  3. 当线程A sleep结束要执行unlock()时,发现synchronizedMap的锁被线程B拿着,无法继续;线程B则在等着线程A释放ReentrantLock。两者互相等待对方的锁,形成死锁。

除此之外,你的实现还有两个额外问题:

  • unlock时错误移除锁实例:computeIfPresent返回null会把map里的锁删掉,要是同一个线程多次重入锁(虽然你当前代码没这么做,但锁设计要支持),第一次unlock就会移除锁,后续unlock会找不到锁实例,抛出异常。
  • map操作的竞态:computeIfPresent和computeIfAbsent是两个独立操作,中间存在间隙,虽然synchronizedMap能保证单个操作原子性,但整体逻辑不是原子的(不过这个在死锁面前是次要问题)。

修正后的实现

咱们换用ConcurrentHashMap(比synchronizedMap并发性能更好),并且把lock()调用移出map的原子操作之外,避免在map锁保护下执行阻塞操作:

final class AccountLock {
    // 用ConcurrentHashMap替代synchronizedMap,并发更高效
    private static final ConcurrentHashMap<String, ReentrantLock> LOCK_MAP = new ConcurrentHashMap<>();
    private static volatile AccountLock INSTANCE;

    private AccountLock() {}

    public static AccountLock getInstance() {
        if (INSTANCE == null) {
            synchronized (AccountLock.class) {
                if (INSTANCE == null) {
                    INSTANCE = new AccountLock();
                }
            }
        }
        return INSTANCE;
    }

    public void lock(String user) {
        // computeIfAbsent是原子操作,确保每个用户对应唯一的ReentrantLock
        ReentrantLock lock = LOCK_MAP.computeIfAbsent(user, k -> new ReentrantLock(true));
        // 拿到锁实例后,在map锁之外执行lock(),不会阻塞其他用户的map操作
        lock.lock();
    }

    public void unlock(String user) {
        ReentrantLock lock = LOCK_MAP.get(user);
        // 先判断当前线程是否持有该锁,避免非法解锁
        if (lock != null && lock.isHeldByCurrentThread()) {
            lock.unlock();
            // 可选:当锁的重入次数为0时,从map中移除锁,避免内存泄漏
            if (lock.getHoldCount() == 0) {
                // 用remove的重载方法,确保只有当前锁实例还在map里时才移除,避免并发问题
                LOCK_MAP.remove(user, lock);
            }
        }
    }
}

为什么这个实现能解决问题?

  1. 避免了死锁:lock()调用是在ConcurrentHashMap的原子操作之外执行的,不会拿着map的锁去等ReentrantLock,也不会拿着ReentrantLock去等map的锁。
  2. 锁实例管理正确:每个用户对应唯一的锁实例,支持重入;解锁时只有当锁完全释放(重入次数为0)才从map移除,避免误删。
  3. 并发性能更好:ConcurrentHashMap的分段锁设计比synchronizedMap的全局锁更适合高并发场景。

你可以把这段代码替换原来的AccountLock,再跑测试就能看到预期的效果:同一个用户的请求会排队处理,不同用户的请求可以并行执行,不会出现死锁或永久等待。

内容的提问来源于stack exchange,提问作者ringdings

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 06:47:55