How to Build a Robust Redis Distributed Lock with AOP and Auto‑Extension in Java

This article explains how to protect time‑consuming business operations using a Redis‑based distributed lock implemented with custom annotations, Spring AOP, and a scheduled thread pool that automatically extends lock expiration to avoid premature release.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
How to Build a Robust Redis Distributed Lock with AOP and Auto‑Extension in Java

1. Business Background

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

2. Analysis Process

Redis is used as a distributed lock, storing lock state centrally to solve the problem of JVM isolation in a cluster and to enforce operation order, protecting user data.

Key steps:

Create a custom annotation @interface to define input parameters.

Add an AOP pointcut to scan for the annotation.

Define an @Aspect component to register the bean and intercept specific methods.

Use ProceedingJoinPoint to intercept method execution before and after.

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

Lock acquisition

Use RedisTemplate.opsForValue().setIfAbsent with a random UUID as the value. After acquiring the lock, set an expiration time so the lock is released automatically when the timeout expires.

If the lock cannot be obtained, the request fails immediately.

Timeout issue

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

Solution: add a "renewal" mechanism

Maintain a scheduled thread pool ( ScheduledExecutorService) that periodically scans pending tasks and extends the lock expiration before it expires.

/**
 * 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);
}

3. Design Scheme

The design includes the following core steps:

Intercept @RedisLock annotation and retrieve parameters.

Perform lock acquisition.

Renew the lock periodically.

Release the lock after business execution.

4. Implementation Details

Enum for lock types

public enum RedisLockTypeEnum {
    ONE("Business1", "Test1"),
    TWO("Business2", "Test2");
    private String code;
    private String desc;
    RedisLockTypeEnum(String code, String desc) { this.code = code; this.desc = desc; }
    public String getCode() { return code; }
    public String getDesc() { return desc; }
    public String getUniqueKey(String key) { return String.format("%s:%s", this.getCode(), key); }
}

Holder for task information

public class RedisLockDefinitionHolder {
    private String businessKey;
    private Long lockTime;
    private Long lastModifyTime;
    private Thread currentTread;
    private int tryCount;
    private int currentCount;
    private Long modifyPeriod;
    public RedisLockDefinitionHolder(String businessKey, Long lockTime, Long lastModifyTime, Thread currentTread, int tryCount) {
        this.businessKey = businessKey;
        this.lockTime = lockTime;
        this.lastModifyTime = lastModifyTime;
        this.currentTread = currentTread;
        this.tryCount = tryCount;
        this.modifyPeriod = lockTime * 1000 / 3;
    }
}

Annotation definition

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RedisLockAnnotation {
    int lockFiled() default 0;
    int tryCount() default 3;
    RedisLockTypeEnum typeEnum();
    long lockTime() default 30;
}

Aspect logic

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

@Around("redisLockPC()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Method method = resolveMethod(pjp);
    RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);
    RedisLockTypeEnum typeEnum = annotation.typeEnum();
    Object[] params = pjp.getArgs();
    String ukString = params[annotation.lockFiled()].toString();
    String businessKey = typeEnum.getUniqueKey(ukString);
    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 get the lock =-");
        }
        redisTemplate.expire(businessKey, annotation.lockTime(), TimeUnit.SECONDS);
        Thread currentThread = Thread.currentThread();
        holderList.add(new RedisLockDefinitionHolder(businessKey, annotation.lockTime(), System.currentTimeMillis(), currentThread, annotation.tryCount()));
        result = pjp.proceed();
        if (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;
}

The scheduled task scans the holderList every two seconds, extends the lock expiration when the remaining time falls below one‑third of the total timeout, and interrupts the thread after exceeding the retry count.

// Scan task queue
private static ConcurrentLinkedQueue<RedisLockDefinitionHolder> holderList = new ConcurrentLinkedQueue();
private static final ScheduledExecutorService SCHEDULER = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
{
    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);
}

5. Testing

A sample controller method demonstrates the lock in action by sleeping for 10 seconds, causing the lock to be renewed and eventually interrupted after the retry limit is reached.

@GetMapping("/testRedisLock")
@RedisLockAnnotation(typeEnum = RedisLockTypeEnum.ONE, lockTime = 3)
public Book testRedisLock(@RequestParam("userId") Long userId) {
    try {
        log.info("Before sleep");
        Thread.sleep(10000);
        log.info("After sleep");
    } catch (Exception e) {
        log.info("has some error", e);
    }
    return null;
}

Log output shows the lock being acquired, renewed three times, and then the thread being interrupted, confirming that the lock prevents concurrent execution of the same business key.

6. Conclusion

For time‑consuming business operations and critical data, duplicate concurrent requests must be prevented; using a Redis distributed lock with AOP interception and automatic renewal provides a reliable solution.

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.

aopconcurrencyredisspringdistributed-lock
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

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.