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