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

Striped ReadWriteLock抛出IllegalMonitorStateException问题求助

并发Multimap锁逻辑异常排查

问题背景

我们基于Guava ForwardingMultimap开发支持同键多值存储的并发Multimap:

  • 初始实现:put/remove对目标键加写锁,get加读锁,单键多线程测试正常
  • 调整原因:put/remove与clear并发时出现数据不一致,于是修改锁逻辑:
    • clear对$public公钥加写锁
    • put/remove先对$public加读锁,再对目标键加写锁,操作完成后依次释放

调整后出现非必现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 ); 
      }
   }
}

原因分析

  1. 锁获取中断未处理:代码中mLockTimeOut=0使用无参lock(),但当线程等待$public读锁时(比如clear正在持有写锁),若被中断会抛出InterruptedException,但当前代码未捕获该异常,导致读锁未成功获取,却在finally块中执行unlock,直接触发IllegalMonitorStateException。
  2. 高并发下触发概率提升:线程数≥45时,$public锁的竞争加剧,线程被中断的概率大幅升高,因此异常更容易复现。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 04:31:12