Master Distributed Locks with Redisson: Deep Dive into Java High‑Performance Architecture

This article explains the concept of distributed locks for ensuring data consistency in clustered environments, outlines common implementation approaches, and provides a comprehensive guide to using Redisson in Java—including Maven setup, YAML configuration, core source‑code analysis, Lua scripts for lock and unlock operations, and practical test cases.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Master Distributed Locks with Redisson: Deep Dive into Java High‑Performance Architecture

Preface

Distributed locks address data consistency problems in clustered or distributed environments. In a single‑process scenario, thread safety can be achieved with volatile, ReentrantLock, synchronized, or classes from the java.util.concurrent package.

Common ways to implement distributed locks are:

Database‑based

Coordination‑system‑based

Cache‑based

Redis commands (e.g., SETNX)

Redis Lua scripts – the method demonstrated in this article uses Redisson

1. Using Redisson

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

Maven dependencies

<!-- Redis dependency for Spring Boot -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.4.0</version>
</dependency>

<!-- Redisson for distributed locking -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.8</version>
</dependency>

YAML configuration

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    jedis:
      pool:
        max-active: 20
        max-wait: -1
        max-idle: 10
        min-idle: 0
    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
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + port);
        return Redisson.create(config);
    }
}

Test case for the distributed lock

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;

@Slf4j
@SpringBootTest(classes = SpringBootDemoxzApplication.class)
public class RedissonTest {
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ThreadPoolTaskExecutor executor;

    private static final String LOCK_TEST_KEY = "redisson:lock:test";
    int n = 500;

    @Test
    public void lockTest() {
        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                RLock fairLock = redissonClient.getFairLock(LOCK_TEST_KEY);
                try {
                    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");
                    fairLock.unlock();
                }
            });
        }
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. Redisson source‑code analysis

Redisson relies on Lua scripts, Netty, and asynchronous Future handling. The lock and unlock processes also use Redis pub/sub.

Simple Lua script for locking (Redisson 3.16.8)

-- If the lock does not exist
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil; -- lock acquired
end
-- If the lock already exists for this thread (re‑entrant)
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‑entrant lock acquired
end
-- Otherwise return remaining TTL (lock acquisition failed)
return redis.call('pttl', KEYS[1]);
Conclusion: Returning nil indicates a successful lock acquisition.

Lua script for unlocking

-- If the lock does not exist for this thread
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    return nil; -- unlock succeeded
end
-- Decrease re‑entrancy counter
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 0; -- 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: Returning 1 means the lock has been completely released.

Core lock acquisition logic (Redisson)

@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 notifications and wait
    CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    try {
        subscribeFuture.get(time, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        acquireFailed(waitTime, unit, threadId);
        return false;
    }
    // Re‑try acquiring the lock until timeout
    while (true) {
        long now = System.currentTimeMillis();
        ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
        if (ttl == null) {
            return true;
        }
        time -= System.currentTimeMillis() - now;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
        // Wait for a signal or timeout
        if (ttl >= 0 && ttl < time) {
            commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
        } else {
            commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
        }
        time -= System.currentTimeMillis() - now;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
    }
} finally {
    unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
}

Helper method that invokes the Lua lock script

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

private <T> RFuture<T> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture<Long> ttlFuture;
    if (leaseTime != -1) {
        ttlFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
        ttlFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }
    return ttlFuture.thenApply(ttl -> {
        if (ttl == null) {
            if (leaseTime != -1) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {
                scheduleExpirationRenewal(threadId);
            }
        }
        return ttl;
    });
}

private <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
        "if (redis.call('exists', KEYS[1]) == 0) then " +
        "redis.call('hincrby', 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]);",
        Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}

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;
        }
    }
}

@Override
public RFuture<Void> unlockAsync(long threadId) {
    RFuture<Boolean> future = unlockInnerAsync(threadId);
    return future.handle((status, ex) -> {
        cancelExpirationRenewal(threadId);
        if (ex != null) {
            throw new CompletionException(ex);
        }
        if (status == null) {
            throw new CompletionException(new IllegalMonitorStateException(
                "attempt to unlock lock, not locked by current thread"));
        }
        return null;
    });
}
Lock flow diagram
Lock flow diagram
Unlock flow diagram
Unlock flow diagram
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.

Javaconcurrencyredisdistributed-lockLuaredisson
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.