Understanding and Fixing Redisson Distributed Lock IllegalMonitorStateException
This article explains the Redisson IllegalMonitorStateException caused by unlocking a lock held by another thread, analyzes the underlying node‑id and thread‑id concepts, and presents several practical solutions to correctly manage distributed locks in Java applications.
The article starts with a light‑hearted anecdote before presenting a real production alarm: an IllegalMonitorStateException indicating an attempt to unlock a lock not owned by the current thread (node id: b9df1975‑5595‑42eb‑beae‑bdc5d67bce49, thread‑id: 52).
It translates the alarm message and explains that the exception occurs when a thread tries to release a lock owned by another thread. The discussion then reviews the meaning of node id and thread‑id in Redisson’s lock implementation, noting that Redisson stores lock information in a Redis hash where the key is the lock name, the field is a composite node id:thread‑id , and the value is the re‑entry count.
A code snippet showing the stack trace is provided:
Exception in thread "thread0" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: b9df1975-5595-42eb-beae-bdc5d67bce49 thread-id: 52
at org.redisson.RedissonLock.lambda$unlockAsync$4(RedissonLock.java:616)
at org.redisson.misc.RedissonPromise.lambda$onComplete$0(RedissonPromise.java:187)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:578)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:552)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:491)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:184)
at org.redisson.misc.RedissonPromise.onComplete(RedissonPromise.java:181)
at org.redisson.RedissonLock.unlockAsync(RedissonLock.java:607)
at org.redisson.RedissonLock.unlock(RedissonLock.java:492)
at com.qsl.ResissonTest.testLock(ResissonTest.java:41)
at java.lang.Thread.run(Thread.java:748)The article then walks through a series of Q&A style explanations that reveal a missing detail: the field value should be the combined node id:thread‑id , not just the thread id, allowing Redisson to distinguish locks across distributed instances.
Next, a humorous code screenshot is shown (the actual code is not reproduced here), followed by an analysis of why the exception occurs when a second thread releases a lock that has already expired because the original holder’s lock lease (3 s) ended before the business logic finished (e.g., 5 s). The article lists several remediation strategies:
Increase wait time : lengthen the lock acquisition timeout, which only reduces the chance of the exception.
Automatic release : remove the explicit finally unlock, relying on the lock’s TTL, which can cause long‑lasting stale locks.
Record acquisition status : check whether the lock was actually acquired before attempting to unlock.
Check holder explicitly : verify that the current thread is the lock owner before releasing.
Owner‑only release : let the thread that holds the lock (or a watchdog) handle release, avoiding cross‑thread unlocks.
Each strategy is illustrated with additional screenshots (preserved as tags) and brief commentary on its pros and cons.
Finally, the article summarizes key take‑aways: the example code is available in the redisson-spring-boot-demo repository; distributed locks should guarantee single‑threaded execution and are best used without a fixed expiration, letting Redisson’s watchdog renew the lease; always release locks explicitly; and the recommended practice is that only the lock owner releases the lock.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.