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.
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: 1000Redisson 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;
});
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
