Striped ReadWriteLock抛出IllegalMonitorStateException问题求助
并发Multimap锁逻辑异常排查
问题背景
我们基于Guava ForwardingMultimap开发支持同键多值存储的并发Multimap:
- 初始实现:put/remove对目标键加写锁,get加读锁,单键多线程测试正常
- 调整原因:put/remove与clear并发时出现数据不一致,于是修改锁逻辑:
- clear对
$public公钥加写锁 - put/remove先对
$public加读锁,再对目标键加写锁,操作完成后依次释放
- clear对
调整后出现非必现IllegalMonitorStateException,仅在线程数≥45时触发,每3-4次测试出现一次。
异常栈
Exception in thread "pool-5-thread-10" java.lang.IllegalMonitorStateException: attempt to unlock read lock, not locked by current thread at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.unmatchedUnlockException(ReentrantReadWriteLock.java:444) at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryReleaseShared(ReentrantReadWriteLock.java:428) at java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(AbstractQueuedSynchronizer.java:1341) at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.unlock(ReentrantReadWriteLock.java:881) at com.google.common.util.concurrent.ForwardingLock.unlock(ForwardingLock.java:48) at org.multimap.rt.locks.ResourceLockManager.releaseReadLock(ResourceLockManager.java:97) at org.multimap.rt.util.ConcurrentMultiMap.put(ConcurrentMultiMap.java:81) at org.multimap.rt.util.OneShotTask.run(ConcurrentMultiMapTest.java:233) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
核心代码
锁管理基础配置
IResourceLockManager mLocks = new ResourceLockManager(); private final String mPublicKey = "$public"; private final int mLockTimeOut = 0;
Put/Remove操作
public boolean put(Object aKey, Object aValue) { mLocks.acquireReadLock(mPublicKey, mLockTimeOut); mLocks.acquireWriteLock(aKey, mLockTimeOut); boolean result = false; try { result = delegate().put(aKey, aValue); } finally { mLocks.releaseWriteLock(aKey); mLocks.releaseReadLock(mPublicKey); } return result; }
Clear操作
public void clear() { mLocks.acquireWriteLock(mPublicKey, mLockTimeOut); try { delegate().clear(); } finally { mLocks.releaseWriteLock(mPublicKey); } }
ResourceLockManager核心逻辑
Striped<ReadWriteLock> lockCache = Striped.lazyWeakReadWriteLock(DEFAULT_PARTITIONS); // acquireReadLock实现 lockCache.get(aKey).readLock().lock(); // releaseReadLock实现 lockCache.get(aKey).readLock().unlock(); // acquireWriteLock实现 lockCache.get(aKey).writeLock().lock(); // releaseWriteLock实现 lockCache.get(aKey).writeLock().unlock();
测试代码
public void TestConcurrentMultiMap() throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(45); for (int i = 0; i < 45; i++) { int num = (int) Math.round(Math.floor(Math.random() * 3)); int oprn = (int) Math.round(Math.random()); executor.execute(new OneShotTask(String.valueOf(num), myMap1, oprn)); } executor.shutdown(); executor.awaitTermination(1000L, TimeUnit.MILLISECONDS); } class OneShotTask implements Runnable { String str; Multimap mMap; int mOprn; OneShotTask(String s, Multimap aMap, int oprn) { str = s; mOprn = oprn; mMap = aMap; } public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } if(mOprn == 0) { mMap.put("key", "value" + str ); } else { mMap.remove("key", "value" + str ); } } }
原因分析
- 锁获取中断未处理:代码中
mLockTimeOut=0使用无参lock(),但当线程等待$public读锁时(比如clear正在持有写锁),若被中断会抛出InterruptedException,但当前代码未捕获该异常,导致读锁未成功获取,却在finally块中执行unlock,直接触发IllegalMonitorStateException。 - 高并发下触发概率提升:线程数≥45时,
$public锁的竞争加剧,线程被中断的概率大幅升高,因此异常更容易复现。 - Striped锁的弱引用特性:
lazyWeakReadWriteLock用弱引用存储锁,极端情况下$public对应的读锁可能被GC回收,重新获取的锁与之前的实例不一致,unlock时也会触发异常,但结合场景,中断导致的未获取锁就unlock是更直接的诱因。
解决方法
方案1:正确处理锁获取的中断与状态跟踪
修改锁获取方法为可中断版本,并跟踪锁的持有状态,确保只有成功获取的锁才会被释放:
// 修改ResourceLockManager的锁获取方法 public void acquireReadLock(Object aKey, long timeoutMs) throws InterruptedException { ReadWriteLock lock = lockCache.get(aKey); if (timeoutMs == 0) { lock.readLock().lockInterruptibly(); } else { if (!lock.readLock().tryLock(timeoutMs, TimeUnit.MILLISECONDS)) { throw new IllegalStateException("Failed to acquire read lock for key: " + aKey); } } } public void acquireWriteLock(Object aKey, long timeoutMs) throws InterruptedException { ReadWriteLock lock = lockCache.get(aKey); if (timeoutMs == 0) { lock.writeLock().lockInterruptibly(); } else { if (!lock.writeLock().tryLock(timeoutMs, TimeUnit.MILLISECONDS)) { throw new IllegalStateException("Failed to acquire write lock for key: " + aKey); } } }
调整put方法,增加锁持有状态标记:
public boolean put(Object aKey, Object aValue) throws InterruptedException { boolean hasPublicReadLock = false; boolean hasKeyWriteLock = false; try { mLocks.acquireReadLock(mPublicKey, mLockTimeOut); hasPublicReadLock = true; mLocks.acquireWriteLock(aKey, mLockTimeOut); hasKeyWriteLock = true; return delegate().put(aKey, aValue); } finally { if (hasKeyWriteLock) { mLocks.releaseWriteLock(aKey); } if (hasPublicReadLock) { mLocks.releaseReadLock(mPublicKey); } } }
方案2:简化锁逻辑(适合小数据量场景)
如果无需细粒度锁,可以调整clear的实现:遍历所有键加写锁后再执行clear,但这种方式性能较低,仅适合数据量不大的场景。
方案3:使用ThreadLocal跟踪锁持有状态
在ResourceLockManager中用ThreadLocal维护当前线程持有的锁集合,unlock前先检查是否持有对应锁,避免非法解锁,但这种方式实现复杂度较高,需注意内存泄漏问题。
内容的提问来源于stack exchange,提问作者arunK
相关产品推荐
相关产品推荐

