ReentrantLock:在finally块解锁时是否需检查锁持有者?
问题分析与解决方案
原代码的核心问题
你的代码在fetchNewToken延迟高时频繁卡顿10秒并抛出大量异常,主要有两个原因:
- 锁释放逻辑错误:try块中已经调用了
lock.unlock(),但finally块又重复执行解锁操作——此时当前线程已经不持有锁,调用unlock()会直接抛出IllegalMonitorStateException,而且你的while循环会反复触发这个异常,消耗线程资源。 - 锁竞争超时:当一个线程持有锁调用
fetchNewToken(耗时1秒),其他线程调用tryLock会等待10秒(你的LOCK_TIMEOUT_SEC值)后超时抛出AppException;如果锁没有被正确释放,会导致更多线程陷入等待超时的循环。
关于finally块的疑问解答
1. 是否需要检查锁持有者?
必须检查。ReentrantLock的核心规则是:只有持有锁的线程才能调用unlock(),否则会抛出IllegalMonitorStateException。如果不做检查,无论当前线程是否持有锁都执行解锁,必然会触发大量异常,甚至破坏锁的并发语义。
2. 其他线程持有锁时能否解锁?
绝对不能。锁的作用是保证同一时间只有一个线程执行临界区代码,如果允许非持有线程解锁,会直接导致临界区代码被多个线程同时执行,引发数据不一致或其他并发问题。
3. 为什么查到的示例没做这个检查?
多数示例会用标记变量记录是否成功获取锁,再结合锁的持有判断,只是写法不同。比如用boolean acquired = false,在成功获取锁后设为true,finally块只在acquired为true时解锁——本质上和检查当前线程持有锁是互补的逻辑,都是为了避免非法解锁。
修正后的代码
public String getToken(String userId, ReentrantLock lock) throws InterruptedException { boolean isLockAcquired = false; try { // 尝试获取锁,超时则抛出异常 if (!lock.tryLock(LOCK_TIMEOUT_SEC, TimeUnit.SECONDS)) { throw new AppException("Could not get lock in " + LOCK_TIMEOUT_SEC + "s"); } isLockAcquired = true; // 调用外部接口获取token String token = fetchNewToken(userId); return token; } finally { // 仅当当前线程持有锁且标记为已获取时,才执行解锁 if (isLockAcquired && lock.isHeldByCurrentThread()) { lock.unlock(); } } }
关键优化点
- 用
isLockAcquired标记是否成功获取锁,避免在未拿到锁的情况下执行解锁。 - 将解锁逻辑统一放在finally块,确保无论
fetchNewToken正常返回还是抛出异常,锁都会被正确释放。 - 结合
lock.isHeldByCurrentThread()做双重校验,避免极端情况下标记与实际锁状态不一致的问题。
内容的提问来源于stack exchange,提问作者WesternGun
相关产品推荐
相关产品推荐

