Backend Development 9 min read

Mastering Distributed Locks with Redisson in Spring Boot 2.7

This guide explains how to implement a robust, re‑entrant distributed lock using Redisson in a Spring Boot 2.7 application, covering basic and asynchronous APIs, Maven dependency setup, YAML configuration, custom annotation, AspectJ implementation, and comprehensive test cases.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Distributed Locks with Redisson in Spring Boot 2.7

1. Introduction

Redisson is a Redis Java client that adds an in‑memory data grid, offering a simple way to use Redis for distributed locks. It works on high‑performance asynchronous, lock‑free Java Redis client and Netty.

JDK and Redis Support

JDK: 1.8~21 Redis: 3.0~7.2

Redisson Distributed Lock

Redisson provides a re‑entrant lock that implements java.util.concurrent.locks.Lock . It uses a Pub/Sub channel to notify waiting threads and a watchdog to extend the lock TTL while the holder is alive (default 30 seconds, configurable via lockWatchdogTimeout ).

Basic Usage

<code>RLock lock = redisson.getLock("myLock");
lock.lock(); // acquire lock
// acquire lock and auto‑unlock after 10 seconds
lock.lock(10, TimeUnit.SECONDS);
// wait up to 100 seconds to acquire lock, auto‑unlock after 10 seconds
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
    try {
        // todo
    } finally {
        lock.unlock();
    }
}</code>

Asynchronous API

<code>RLock rLock = redisson.getLock(key);
rLock.lock();
RFuture<Boolean> lockFuture = rLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
lockFuture.whenComplete((res, exception) -> {
    // todo
    rLock.unlockAsync();
});</code>

For full details, refer to the official Redisson documentation.

2. Practical Example

2.1 Dependency Management

<code>&lt;dependency&gt;
    &lt;groupId&gt;org.redisson&lt;/groupId&gt;
    &lt;artifactId&gt;redisson-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;3.18.0&lt;/version&gt;
&lt;/dependency&gt;</code>

2.2 Configuration File

<code>spring:
  redis:
    redisson:
      file: classpath:redisson.yaml</code>

The redisson.yaml file contains all Redisson settings, for example:

<code>singleServerConfig:
  idleConnectionTimeout: 10000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  password: xxxooo
  subscriptionsPerConnection: 5
  clientName: null
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  connectionMinimumIdleSize: 24
  connectionPoolSize: 64
  database: 14
  dnsMonitoringInterval: 5000
  threads: 16
  nettyThreads: 32
  codec: !&lt;org.redisson.codec.Kryo5Codec&gt; {}
  transportMode: "NIO"</code>

2.3 Core Class Definition

Custom Annotation

<code>@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DLock {
    /** distributed lock key */
    String value() default "";
}</code>

The value attribute defines the lock key; if empty, a key is generated from the class and method parameters.

Aspect Definition

<code>@Component
@Aspect
public class DistributedLockAspect {
    private final RedissonClient redisson;
    public DistributedLockAspect(RedissonClient redisson) {
        this.redisson = redisson;
    }
    @Pointcut("@annotation(lock)")
    private void lockpc(DLock lock) {}
    @Around("lockpc(lock)")
    public Object lockAround(ProceedingJoinPoint pjp, DLock lock) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Object[] args = pjp.getArgs();
        String key = lock.value();
        if ("".equals(key)) {
            key = configKey(signature.getDeclaringType(), method)
                    .replaceAll("[^a-zA-Z0-9]", "");
        } else {
            StandardEvaluationContext context = new StandardEvaluationContext();
            DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(key);
            key = expression.getValue(context, String.class);
        }
        RLock rLock = redisson.getLock(key);
        rLock.lock();
        try {
            return pjp.proceed();
        } finally {
            rLock.unlock();
        }
    }
    private String configKey(Class<?> targetType, Method method) {
        StringBuilder builder = new StringBuilder();
        builder.append(targetType.getSimpleName());
        builder.append('#').append(method.getName()).append('(');
        for (Class<?> param : method.getParameterTypes()) {
            builder.append(param.getSimpleName()).append(',');
        }
        if (method.getParameterTypes().length > 0) {
            builder.deleteCharAt(builder.length() - 1);
        }
        return builder.append(')').toString();
    }
}
</code>

Testing

Test method using SpEL to generate a lock key:

<code>@DLock("'person:' + #person.id + ':' + #cateId")
public void create(Person person, Integer cateId) {
    System.out.printf("Current thread: %s%n", Thread.currentThread().getName());
    // todo
    sleep(5);
}</code>

JUnit test that spawns multiple threads to invoke create concurrently:

<code>@Test
public void testCreate() {
    int size = 5;
    CountDownLatch cdl = new CountDownLatch(size);
    CountDownLatch nums = new CountDownLatch(size);
    Thread[] threads = new Thread[size];
    for (int i = 0; i < size; i++) {
        final int n = i;
        threads[i] = new Thread(() -> {
            try { nums.countDown(); nums.await(); } catch (InterruptedException e) { e.printStackTrace(); }
            Person person = new Person();
            person.setId(2L);
            person.setName("张三");
            lockService.create(person, 666);
            cdl.countDown();
        }, "T - " + n);
    }
    for (Thread t : threads) { t.start(); }
    try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); }
}
</code>

When all threads use the same parameters, they acquire the lock sequentially, producing timestamps like:

Sequential execution output
Sequential execution output

When each thread uses a different key (different request parameters), all threads run concurrently, and the Redis keys differ, as shown below:

Redis keys for different parameters
Redis keys for different parameters

Note: The aspect does not handle exceptions that may arise during SpEL evaluation.

This completes the article.

JavaRedisSpring BootDistributed LockAspectJRedisson
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.