Implementing Reentrant Distributed Locks with Redis: ThreadLocal and Redis Hash Solutions

This article explains the concept of reentrant distributed locks and provides two Java implementations—one using ThreadLocal for local counting and another using Redis Hash with Lua scripts—detailing the code, pitfalls, and integration with Spring Boot.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Implementing Reentrant Distributed Locks with Redis: ThreadLocal and Redis Hash Solutions

When integrating an external system into a new platform, developers often face the challenge of replicating functionality while ensuring backward compatibility and data migration, which can lead to complex and time‑consuming projects.

The article introduces the concept of reentrant locks, quoting the Wikipedia definition and explaining that a reentrant lock allows the same thread to acquire the lock multiple times without deadlock, unlike a non‑reentrant lock which requires the lock to be released before reacquisition.

Two implementation approaches are presented:

ThreadLocal Based Solution

Using Java's ThreadLocal, each thread maintains its own Map where the key is the lock name and the value is the reentry count. The global variable LOCKS is defined as:

private static ThreadLocal<Map<String, Integer>> LOCKS = ThreadLocal.withInitial(HashMap::new);

The lock acquisition method increments the count if the lock is already held, otherwise it delegates to a Redis lock and sets the count to 1:

public Boolean tryLock(String lockName, String request, long leaseTime, TimeUnit unit) {<br/>    Map<String, Integer> counts = LOCKS.get();<br/>    if (counts.containsKey(lockName)) {<br/>        counts.put(lockName, counts.get(lockName) + 1);<br/>        return true;<br/>    } else {<br/>        if (redisLock.tryLock(lockName, request, leaseTime, unit)) {<br/>            counts.put(lockName, 1);<br/>            return true;<br/>        }<br/>    }<br/>    return false;<br/>}

The unlock method decrements the count and releases the Redis lock when the count reaches zero, throwing an IllegalMonitorStateException if an unlock is attempted by a thread that does not own the lock.

public void unlock(String lockName, String request) {<br/>    Map<String, Integer> counts = LOCKS.get();<br/>    if (counts.getOrDefault(lockName, 0) <= 1) {<br/>        counts.remove(lockName);<br/>        Boolean result = redisLock.unlock(lockName, request);<br/>        if (!result) {<br/>            throw new IllegalMonitorStateException("attempt to unlock lock, not locked by lockName:" + lockName + " with request: " + request);<br/>        }<br/>    } else {<br/>        counts.put(lockName, counts.get(lockName) - 1);<br/>    }<br/>}

While this approach is simple and efficient, it suffers from expiration‑time inconsistencies and cannot handle reentrancy across different threads or processes.

Redis Hash Based Solution

This method stores the reentry count in a Redis Hash, using Lua scripts to perform atomic operations. The lock acquisition Lua script checks existence, creates or increments the hash field, and sets an expiration:

---- 1 represents true<br/>---- 0 represents false<br/><br/>if (redis.call('exists', KEYS[1]) == 0) then<br/>    redis.call('hincrby', KEYS[1], ARGV[2], 1);<br/>    redis.call('pexpire', KEYS[1], ARGV[1]);<br/>    return 1;<br/>end;<br/>if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then<br/>    redis.call('hincrby', KEYS[1], ARGV[2], 1);<br/>    redis.call('pexpire', KEYS[1], ARGV[1]);<br/>    return 1;<br/>end;<br/>return 0;

The Java method loads the script and executes it via StringRedisTemplate:

String lockLuaScript = IOUtils.toString(ResourceUtils.getURL("classpath:lock.lua").openStream(), Charsets.UTF_8);<br/>DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<>(lockLuaScript, Boolean.class);<br/>public Boolean tryLock(String lockName, String request, long leaseTime, TimeUnit unit) {<br/>    long internalLockLeaseTime = unit.toMillis(leaseTime);<br/>    return stringRedisTemplate.execute(lockScript, Lists.newArrayList(lockName), String.valueOf(internalLockLeaseTime), request);<br/>}

The unlock Lua script decrements the counter and deletes the hash when the count reaches zero:

if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then<br/>    return nil;<br/>end;<br/>local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);<br/>if (counter > 0) then<br/>    return 0;<br/>else<br/>    redis.call('del', KEYS[1]);<br/>    return 1;<br/>end;<br/>return nil;

The corresponding Java unlock method handles the three possible return values (1 = success, 0 = decremented, null = other thread attempted unlock):

String unlockLuaScript = IOUtils.toString(ResourceUtils.getURL("classpath:unlock.lua").openStream(), Charsets.UTF_8);<br/>DefaultRedisScript<Long> unlockScript = new DefaultRedisScript<>(unlockLuaScript, Long.class);<br/>public void unlock(String lockName, String request) {<br/>    Long result = stringRedisTemplate.execute(unlockScript, Lists.newArrayList(lockName), request);<br/>    if (result == null) {<br/>        throw new IllegalMonitorStateException("attempt to unlock lock, not locked by lockName:" + lockName + " with request: " + request);<br/>    }<br/>}

Additional considerations include version compatibility of spring-data-redis (requiring ≥ 2.1.9 for cluster mode), handling of Lua‑to‑Redis type conversions (numbers, booleans, nil), and potential pitfalls when using raw Jedis connections.

Summary

The reentrant distributed lock relies on counting lock acquisitions; the ThreadLocal approach is straightforward but limited to a single JVM thread, while the Redis Hash approach works across processes at the cost of added complexity and Lua scripting. Both implementations integrate with Spring Boot and illustrate common issues such as lock expiration and type conversion.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaredisSpring Bootdistributed-lockThreadLocalLuareentrant
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.