Backend Development 30 min read

Understanding Redisson Distributed Locks: Implementation, Reentrancy, Fairness, and Watchdog Mechanism

This article explains the Redisson library’s distributed lock implementation in Java, covering its basic concepts, custom lock creation with Lua scripts, reentrant and fair lock mechanisms, the watchdog renewal process, and practical usage examples with code snippets.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Understanding Redisson Distributed Locks: Implementation, Reentrancy, Fairness, and Watchdog Mechanism

This article provides an in-depth analysis of Redisson, a Java client that builds a distributed in‑memory data grid on top of Redis, and its support for various distributed synchronization primitives.

Redisson Overview

Redisson offers a rich set of distributed objects (BitSet, Set, Map, Queue, etc.) and services (locks, semaphores, publish/subscribe, scheduler, etc.) that simplify Redis usage for Java developers.

Basic Distributed Lock with Lua

A simple lock can be implemented by using SETNX combined with an expiration, but to guarantee atomicity a Lua script is required. The following script acquires the lock and sets a timeout:

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;
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]);

The corresponding unlock script decrements the counter and removes the key when the count reaches zero:

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

Reentrant Lock (RLock)

Redisson’s RLock implements a re‑entrant lock by storing a hash where the field is a combination of UUID and thread ID and the value is the lock count. The lock acquisition logic uses the same Lua script as above, while unlocking follows the unlock script, ensuring that the same thread can acquire the lock multiple times without deadlock.

Watchdog (Lock Renewal)

If a lock is created without an explicit lease time, Redisson starts a watchdog task that periodically (≈ 1/3 of the lease time) executes a renewal script to extend the expiration, preventing accidental lock release while the owning thread is still working.

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
  redis.call('pexpire', KEYS[1], ARGV[1]);
  return 1;
end;
return 0;

This renewal runs asynchronously via Netty’s timeout mechanism and reschedules itself until the lock is explicitly released.

Fair Lock (RedissonFairLock)

For FIFO fairness, Redisson uses a Redis List (queue) and a ZSet (timeout set). Each waiting thread pushes its identifier onto the list and adds a score (current time + wait timeout) to the sorted set. The lock acquisition script first cleans expired entries, then grants the lock only to the head of the queue, ensuring first‑come‑first‑served ordering.

-- Clean expired queue head
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;
-- Acquire if lock absent and head matches
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;
-- Re‑enter if already owned
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 remaining TTL for waiting threads
local ttl = redis.call('pttl', KEYS[1]);
return ttl;

The fair lock also publishes an unlock message via Redis Pub/Sub so waiting threads are notified when the lock becomes available.

Conclusion

Redisson combines Java concurrency primitives (AQS), Netty’s asynchronous I/O, and Redis data structures to deliver robust distributed locking with features such as re‑entrancy, automatic lease renewal, and FIFO fairness. The library also provides higher‑level constructs like MultiLock and the RedLock algorithm for multi‑node scenarios.

JavaRedisDistributed LockLuaRedissonwatchdogFair LockReentrant Lock
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login 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.