Implementing a Redis Distributed Lock with AOP and Automatic Renewal in Spring Boot
This article explains how to protect time‑consuming business operations in a Spring Boot application by creating a custom @RedisLock annotation, using Redis as a distributed lock via AOP, implementing automatic lock renewal with a ScheduledExecutorService, and handling lock release and thread interruption.
Business background
Some business requests involve time‑consuming operations that require locking to prevent concurrent modifications and to protect database integrity.
Analysis
Redis is used as a distributed lock, storing lock state in Redis so that multiple JVM instances can share lock information. The design includes creating a custom annotation, adding an AOP pointcut, and implementing lock acquisition, renewal, and release.
Core steps
Define a custom annotation @RedisLockAnnotation with parameters such as lock key field, retry count, lock type, and lock timeout.
Implement an AOP aspect ( RedisLockAspect.java ) that intercepts methods annotated with @RedisLockAnnotation .
In the @Around advice, acquire the lock using RedisTemplate.opsForValue().setIfAbsent , set an expiration, and add the task to a renewal queue.
After method execution, delete the lock key in a finally block.
Use a ScheduledExecutorService to periodically scan the queue and extend the lock expiration when the remaining time falls below one‑third of the total timeout.
Interrupt the thread if the retry limit is exceeded.
Lock acquisition
The lock is obtained by generating a UUID as the value and calling setIfAbsent . If the lock cannot be acquired, an exception is thrown. The key’s TTL is set to the configured lock time.
Timeout problem
If the intercepted method takes longer than the lock’s TTL, the lock may expire early, allowing another thread to acquire it and cause data inconsistency.
Solution – automatic renewal
A single‑threaded ScheduledExecutorService runs every 2 seconds, checks each RedisLockDefinitionHolder in a concurrent queue, and extends the TTL when needed. It also removes expired entries and interrupts threads that exceed the retry count.
private static final ScheduledExecutorService SCHEDULER =
new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder()
.namingPattern("redisLock-schedule-pool")
.daemon(true)
.build());
static {
SCHEDULER.scheduleAtFixedRate(() -> {
Iterator
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 demonstrates the annotation usage. When the simulated business logic sleeps longer than the lock timeout, the renewal thread keeps the lock alive; after the retry limit is reached, the thread is interrupted and an InterruptedException is logged.
@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;
}Conclusion
Using Redis as a distributed lock with AOP and an automatic renewal mechanism effectively protects time‑consuming operations from concurrent access, ensures data consistency, and demonstrates practical usage of AOP, scheduled thread pools, and thread interruption in Java.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.