Backend Development 18 min read

Using Redisson for Distributed Locks in Java: Configuration, Code Samples, and Source‑Code Analysis

This article explains how distributed locks solve data‑consistency problems in clustered environments, introduces Redisson as a Redis‑based locking library, provides Maven and YAML configuration, shows Java code for Redisson setup and test cases, and analyses the underlying Lua scripts and source‑code mechanisms.

Top Architect
Top Architect
Top Architect
Using Redisson for Distributed Locks in Java: Configuration, Code Samples, and Source‑Code Analysis

Preface

Distributed locks are primarily used to solve data‑consistency problems in clustered or distributed environments. In a single‑machine scenario, thread safety can be achieved with Java constructs such as volatile , ReentrantLock , synchronized , and classes from the java.util.concurrent package.

The main implementation approaches for distributed locks are:

Database‑based locks

Coordination‑system‑based locks (e.g., Zookeeper)

Cache‑based locks, especially Redis‑based locks

1. Using Redisson

Redisson supports single‑node, master‑slave, sentinel, and cluster modes; the following example uses the single‑node mode.

Maven dependencies:

org.springframework.boot
spring-boot-starter-data-redis
2.4.0
org.redisson
redisson
3.16.8

YAML configuration (application.yml):

spring:
  redis:
    # Redis database index (default 0)
    database: 0
    # Redis server address
    host: 127.0.0.1
    # Redis server port
    port: 6379
    # Redis password (empty by default)
    password:
    jedis:
      pool:
        # Maximum connections in the pool (negative means no limit)
        max-active: 20
        # Maximum wait time for a connection (negative means no limit)
        max-wait: -1
        # Maximum idle connections
        max-idle: 10
        # Minimum idle connections
        min-idle: 0
        # Connection timeout (ms)
        timeout: 1000

Redisson configuration class:

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

    @Bean
    @ConditionalOnMissingBean
    public RedissonClient redissonClient() {
        Config config = new Config();
        // Single‑server mode – set address and password (if any)
        System.out.println(redisHost);
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + port);
        return Redisson.create(config);
    }
}

Test case demonstrating a fair lock with high concurrency:

package com.example.aopdemo;

import com.example.aopdemo.springbootaopdemo.SpringBootDemoxzApplication;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @ClassName RedissonTest
 * @Description Redisson test case
 * @Author 阿Q
 * @Date 2022/11/26
 */
@Slf4j
@SpringBootTest(classes = SpringBootDemoxzApplication.class)
public class RedissonTest {
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ThreadPoolTaskExecutor executor;
    // Redisson distributed‑lock key
    private static final String LOCK_TEST_KEY = "redisson:lock:test";
    int n = 500;
    /**
     * Distributed lock test case
     */
    @Test
    public void lockTest() {
        // Simulate high‑concurrency requests with a loop + multithreading
        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                // Obtain a fair lock (FIFO) for easier testing
                RLock fairLock = redissonClient.getFairLock(LOCK_TEST_KEY);
                try {
                    // tryLock(waitTimeout, leaseTime)
                    boolean lock = fairLock.tryLock(3000, 30, TimeUnit.MILLISECONDS);
                    if (lock) {
                        log.info("Thread:" + Thread.currentThread().getName() + " acquired lock");
                        log.info("Remaining count:{}", --n);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log.info("Thread:" + Thread.currentThread().getName() + " releasing lock");
                    // Always unlock
                    fairLock.unlock();
                }
            });
        }
        try {
            // Keep the main thread alive for 10 seconds
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. Redisson Source Analysis

Redisson relies on Lua scripts, Netty, and various asynchronous Future mechanisms. During lock and unlock operations it also makes use of Redis pub/sub.

Simple Overview of the Lua Scripts (Redisson 3.16.8)

Understanding the lock implementation requires a basic grasp of the Lua scripts used.

Lock Script

KEYS[1] – lock name

ARGV[1] – automatic expiration time (ms, default 30 s)

ARGV[2] – hash field key (uuid+threadId)

-- If the lock does not exist
if (redis.call('exists', KEYS[1]) == 0) then
    -- Increment re‑entry count (initially 0)
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    -- Set expiration time
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil; -- lock acquired
end
-- If the lock already exists and belongs to the same thread
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; -- re‑entry successful
end
-- Otherwise return remaining TTL (lock acquisition failed)
return redis.call('pttl', KEYS[1]);
Conclusion: Only when the script returns nil does the lock succeed.

Unlock Script

KEYS[1] – lock name

KEYS[2] – pub/sub channel channel=redisson_lock__channel:{lock_name}

ARGV[1] – unlock message (0)

ARGV[2] – watchdog renewal time

ARGV[3] – hash field key (uuid+threadId)

-- If the lock does not exist for this thread
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    return nil; -- unlock succeeded
end
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 0; -- re‑entry count decreased, lock still held
else
    redis.call('del', KEYS[1]);
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1; -- lock fully released
end
return nil;
Conclusion: Only when the script returns 1 is the lock truly released.

Watchdog Renewal Script

Executed every 10 seconds (30 s / 3) to extend the lock TTL.

KEYS[1] – lock name

ARGV[1] – renewal time (ms)

-- If the lock owned by this thread exists
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return 1; -- renewal succeeded
end
return 0; -- lock no longer exists, stop renewing

Source Code Highlights

Lock acquisition logic (tryLock)

@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    long time = unit.toMillis(waitTime);
    long current = System.currentTimeMillis();
    long threadId = Thread.currentThread().getId();
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
    if (ttl == null) {
        return true;
    }
    time -= System.currentTimeMillis() - current;
    if (time <= 0) {
        acquireFailed(waitTime, unit, threadId);
        return false;
    }
    // Subscribe to unlock messages, wait, retry, etc.
    // ... (omitted for brevity) ...
    return false;
}

Internal lock acquisition (tryAcquire)

private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

Asynchronous lock acquisition (tryAcquireAsync)

private
RFuture
tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture
ttlRemainingFuture;
    if (leaseTime != -1) {
        ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
        ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }
    // Process result, schedule renewal if needed, etc.
    // ... (omitted for brevity) ...
    return new CompletableFutureWrapper<>(f);
}

Unlock logic

@Override
public void unlock() {
    try {
        get(unlockAsync(Thread.currentThread().getId()));
    } catch (RedisException e) {
        if (e.getCause() instanceof IllegalMonitorStateException) {
            throw (IllegalMonitorStateException) e.getCause();
        } else {
            throw e;
        }
    }
}

The article then shifts to promotional material for a ChatGPT community, offering free accounts, courses, and interview resources. While not technical, it is part of the original source.

Conclusion

Redisson implements distributed locking by delegating the core lock/unlock operations to concise Lua scripts, leveraging Redis’ atomic commands, pub/sub, and a watchdog renewal mechanism. Understanding these scripts and the surrounding Java wrapper code provides deep insight into building reliable distributed locks in Java applications.

JavaconcurrencyRedisDistributed LockRedissonLua Script
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.