基于ReentrantLock实现用户级锁遇死锁问题求助
问题排查与解决方案
首先,咱们来拆解下你代码里导致死锁和永久等待的核心问题:
核心死锁原因
你的AccountLock实现里,lock和unlock操作在synchronizedMap的锁保护下执行了阻塞的lock()/unlock()调用,这直接引发了死锁场景:
- 线程A(处理用户1234)成功获取了该用户的
ReentrantLock,进入sleep阶段;此时它后续需要调用unlock(),而unlock()里的computeIfPresent必须先拿到synchronizedMap的锁。 - 线程B(同样处理用户1234)调用
lock(),先执行computeIfPresent——这个方法会先获取synchronizedMap的锁,然后尝试调用lock.lock();但锁已经被线程A持有,线程B就卡在这一步,同时一直持有synchronizedMap的锁不放。 - 当线程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); } } } }
为什么这个实现能解决问题?
- 避免了死锁:
lock()调用是在ConcurrentHashMap的原子操作之外执行的,不会拿着map的锁去等ReentrantLock,也不会拿着ReentrantLock去等map的锁。 - 锁实例管理正确:每个用户对应唯一的锁实例,支持重入;解锁时只有当锁完全释放(重入次数为0)才从map移除,避免误删。
- 并发性能更好:
ConcurrentHashMap的分段锁设计比synchronizedMap的全局锁更适合高并发场景。
你可以把这段代码替换原来的AccountLock,再跑测试就能看到预期的效果:同一个用户的请求会排队处理,不同用户的请求可以并行执行,不会出现死锁或永久等待。
内容的提问来源于stack exchange,提问作者ringdings
相关产品推荐
相关产品推荐

