Master Redisson Distributed Locks: From Simple Reentrant to Fair Lock with WatchDog

This article explains how Redisson implements distributed locks on Redis, covering basic concepts, a simple lock with Lua scripts, reentrancy handling, automatic lock renewal via WatchDog, and the advanced fair lock mechanism that ensures FIFO ordering using Redis lists and sorted sets.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Master Redisson Distributed Locks: From Simple Reentrant to Fair Lock with WatchDog

1. Redisson Overview

Redisson is a Java in‑memory data grid built on Redis that provides distributed objects and services such as locks.

What is Redisson?

It abstracts Redis into higher‑level distributed tools, separating concerns so developers can focus on business logic.

2. Distributed Lock

How to implement a distributed lock?

Various approaches exist (ZooKeeper, DB row locks, Redis setNx), but this article focuses on Redisson’s Redis‑based solution.

Simple Redis lock example

public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
    return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}

public void unlock(String lockName, String uuid) {
    if (uuid.equals(redisTemplate.opsForValue().get(lockName))) {
        redisTemplate.opsForValue().del(lockName);
    }
}

This version is not atomic for get and del, so a Lua script is introduced.

Lua script for atomic unlock

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

Making the lock re‑entrant

Re‑entrancy stores lockName, threadId, and a count in a Redis hash. The lock is acquired by incrementing the count; unlocking decrements it and removes the hash when the count reaches zero.

hset lockname1 threadId 1
hincrby lockname1 threadId 1
hget lockname1 threadId

3. Redisson Distributed Lock

Dependency

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency>

Configuration

@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.password}")
    private String password;
    private int port = 6379;

    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer()
              .setAddress("redis://" + redisHost + ":" + port)
              .setPassword(password);
        config.setCodec(new JsonJacksonCodec());
        return Redisson.create(config);
    }
}

Using the lock

RLock rLock = redissonClient.getLock("myLock");
if (rLock.tryLock(expireTime, TimeUnit.MILLISECONDS)) {
    // business logic
    rLock.unlock();
}

4. RLock Core

RLock implements java.util.concurrent.locks.Lock and adds asynchronous methods returning RFuture. The key method tryLockInnerAsync executes a Lua script that creates or updates a Redis hash and sets an expiration.

if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
-- re‑entrant case
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
return redis.call('pttl', KEYS[1]);

If no lease time is provided, Redisson starts a WatchDog that periodically renews the lock expiration.

WatchDog lock renewal

private void scheduleExpirationRenewal(long threadId) {
    // registers a Netty timeout that runs every leaseTime/3
    // calls renewExpirationAsync(threadId) which executes:
    // if (hexists(KEYS[1], ARGV[2]) == 1) then
    //     pexpire(KEYS[1], ARGV[1]);
    //     return 1;
    // else return 0;
}

5. Fair Lock

RedissonFairLock ensures FIFO ordering using a Redis list ( redisson_lock_queue:{name}) and a sorted set ( redisson_lock_timeout:{name}).

-- 1. Clean expired queue heads
while true do
    local first = redis.call('lindex', KEYS[2], 0);
    if first == false then break end;
    local timeout = tonumber(redis.call('zscore', KEYS[3], first));
    if timeout <= tonumber(ARGV[4]) then
        redis.call('zrem', KEYS[3], first);
        redis.call('lpop', KEYS[2]);
    else break end;
end;

-- 2. First‑time lock acquisition when queue is empty or head matches current thread
if (redis.call('exists', KEYS[1]) == 0) and
   ((redis.call('exists', KEYS[2]) == 0) or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then
    redis.call('lpop', KEYS[2]);
    redis.call('zrem', KEYS[3], ARGV[2]);
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;

-- 3. Re‑entrant case
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;

-- 4. Return current thread TTL
local timeout = redis.call('zscore', KEYS[3], ARGV[2]);
if timeout ~= false then
    return timeout - tonumber(ARGV[3]) - tonumber(ARGV[4]);
end;

-- 5. Compute tail TTL and enqueue
local last = redis.call('lindex', KEYS[2], -1);
local ttl = (last ~= false and last ~= ARGV[2])
          and tonumber(redis.call('zscore', KEYS[3], last)) - tonumber(ARGV[4])
          or redis.call('pttl', KEYS[1]);
local newScore = ttl + tonumber(ARGV[3]) + tonumber(ARGV[4]);
if redis.call('zadd', KEYS[3], newScore, ARGV[2]) == 1 then
    redis.call('rpush', KEYS[2], ARGV[2]);
end;
return ttl;

The algorithm cleans expired queue heads, attempts immediate acquisition if the head matches, handles re‑entrancy, returns remaining TTL, and otherwise enqueues the thread with a calculated score that preserves FIFO order.

6. Summary

Redisson provides a sophisticated distributed lock implementation that combines Redis Lua scripts, Netty asynchronous futures, and Java concurrency concepts. It supports re‑entrant locks with automatic WatchDog renewal and a fair lock that guarantees FIFO ordering using Redis lists and sorted sets, making it a powerful tool for backend Java services.

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.

javaredisdistributed-lockLuaredissonWatchdogfair lock
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.