How to Implement a Robust Redis Distributed Lock with Spring AOP and Auto‑Renewal

This article explains the design and implementation of a Redis‑based distributed lock using Spring AOP, covering annotation creation, pointcut definition, lock acquisition, automatic renewal via a scheduled thread pool, error handling, and testing to ensure safe concurrent access to critical business data.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
How to Implement a Robust Redis Distributed Lock with Spring AOP and Auto‑Renewal

Business Background

Some business requests involve time‑consuming operations that require locking to prevent concurrent modifications and to protect database integrity.

Analysis Process

Redis is used as a distributed lock store, centralising lock state and solving the problem of JVM isolation in a cluster.

Design Flow

Create a custom annotation @interface with input parameters.

Add an AOP pointcut to scan the specific annotation.

Define an @Aspect bean to intercept the target method.

Use ProceedingJoinPoint to wrap method execution.

Lock before execution and delete the key after the task completes.

Core steps: lock, unlock, and renew

Lock Acquisition

The implementation uses RedisTemplate.opsForValue().setIfAbsent to set a key only if it does not exist, assigning a random UUID as the value.

After acquiring the lock, an expiration time is set so the key is automatically released when it expires.

Only the first request that successfully sets the key proceeds; subsequent requests fail and exit.

Timeout Issue

If the intercepted method takes longer than the lock timeout, the key may be released prematurely, allowing another thread to acquire the lock and cause data inconsistency.

Solution: Add a Renewal Mechanism

Task Not Completed, Lock Not Released

A ScheduledExecutorService runs every 2 seconds, scanning a queue of tasks and extending the expiration time when the remaining lifetime falls below one‑third of the total timeout.

/**
 * Thread pool, each JVM uses one thread to maintain keyAliveTime, scheduled execution
 */
private static final ScheduledExecutorService SCHEDULER =
    new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
static {
    SCHEDULER.scheduleAtFixedRate(() -> {
        // do something to extend time
    }, 0, 2, TimeUnit.SECONDS);
}

Design Solution

The final design includes four core steps:

Intercept @RedisLock annotation and obtain parameters.

Perform lock operation.

Execute renewal logic.

Release the lock after business execution.

Practical Implementation

The RedisLockAspect class is divided into three parts:

Pointcut Definition

@Pointcut("@annotation(cn.sevenyuan.demo.aop.lock.RedisLockAnnotation)")
public void redisLockPC() {}

Around Advice (Lock & Unlock)

public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Method method = resolveMethod(pjp);
    RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);
    String businessKey = annotation.typeEnum().getUniqueKey(pjp.getArgs()[annotation.lockFiled()].toString());
    String uniqueValue = UUID.randomUUID().toString();
    Object result = null;
    try {
        boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue);
        if (!isSuccess) {
            throw new Exception("You can't do it, because another has got the lock");
        }
        redisTemplate.expire(businessKey, annotation.lockTime(), TimeUnit.SECONDS);
        holderList.add(new RedisLockDefinitionHolder(businessKey, annotation.lockTime(), System.currentTimeMillis(), Thread.currentThread(), annotation.tryCount()));
        result = pjp.proceed();
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedException("You had been interrupted");
        }
    } catch (InterruptedException e) {
        log.error("Interrupt exception, rollback transaction", e);
        throw new Exception("Interrupt exception, please send request again");
    } catch (Exception e) {
        log.error("has some error, please check again", e);
    } finally {
        redisTemplate.delete(businessKey);
        log.info("release the lock, businessKey is [" + businessKey + "]");
    }
    return result;
}

Renewal Operation

private static final ConcurrentLinkedQueue<RedisLockDefinitionHolder> holderList = new ConcurrentLinkedQueue<>();
private static final ScheduledExecutorService SCHEDULER = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
static {
    SCHEDULER.scheduleAtFixedRate(() -> {
        Iterator<RedisLockDefinitionHolder> iterator = holderList.iterator();
        while (iterator.hasNext()) {
            RedisLockDefinitionHolder holder = iterator.next();
            if (holder == null) { iterator.remove(); continue; }
            if (redisTemplate.opsForValue().get(holder.getBusinessKey()) == null) { iterator.remove(); continue; }
            if (holder.getCurrentCount() > holder.getTryCount()) {
                holder.getCurrentTread().interrupt();
                iterator.remove();
                continue;
            }
            long curTime = System.currentTimeMillis();
            boolean shouldExtend = holder.getLastModifyTime() + holder.getModifyPeriod() <= curTime;
            if (shouldExtend) {
                holder.setLastModifyTime(curTime);
                redisTemplate.expire(holder.getBusinessKey(), holder.getLockTime(), TimeUnit.SECONDS);
                log.info("businessKey : [" + holder.getBusinessKey() + "], try count : " + holder.getCurrentCount());
                holder.setCurrentCount(holder.getCurrentCount() + 1);
            }
        }
    }, 0, 2, TimeUnit.SECONDS);
}

Testing

A sample controller method is annotated with @RedisLockAnnotation and simulates a long‑running task using Thread.sleep. The logs show lock acquisition, renewal attempts, and eventual interruption when the retry limit is exceeded.

2020-04-04 14:55:50.864  INFO  ... : 睡眠执行前
2020-04-04 14:55:52.855  INFO  ... : businessKey : [Business1:1024], try count : 0
... (subsequent renewal logs) ...
java.lang.InterruptedException: sleep interrupted

Conclusion

For time‑consuming business operations, a distributed lock prevents concurrent modifications and ensures data correctness. The article demonstrates annotation‑driven lock management, AOP interception, automatic renewal via a scheduled thread pool, and proper lock release, providing a practical reference for implementing reliable distributed locks in Java back‑ends.

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-lockspring-aopScheduledExecutorService
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.