Implementing a Distributed Redis Lock with AOP and Automatic Renewal in Java
This article explains how to design and implement a distributed lock using Redis, Spring AOP annotations, and a scheduled executor to automatically extend lock expiration, providing complete Java code examples, configuration details, testing procedures, and best‑practice recommendations for handling long‑running business operations safely.
The article begins by describing a business scenario where time‑consuming operations require locking to prevent concurrent modifications and ensure data integrity.
It proposes using Redis as a distributed lock, storing lock state in Redis to synchronize multiple JVM instances and enforce operation order.
Design Process
The design workflow includes:
Creating a custom annotation @RedisLockAnnotation to mark methods that need locking.
Defining an AOP pointcut that intercepts methods annotated with the custom annotation.
Implementing an @Aspect class that registers a bean and intercepts the target method.
Using ProceedingJoinPoint to execute the original method before and after lock handling.
Locking before method execution and deleting the key after completion.
Lock Acquisition
The lock is acquired with RedisTemplate.opsForValue().setIfAbsent , generating a random UUID as the lock value and setting an expiration time. Only the first request that successfully sets the key proceeds; others fail and return an error.
Timeout and Renewal
If the intercepted method takes longer than the lock timeout, the lock may be released prematurely, causing concurrent access. To avoid this, a renewal mechanism is introduced using a ScheduledExecutorService that periodically scans a task queue and extends the lock expiration before it expires.
/**
* Thread pool, each JVM uses one thread to maintain keyAliveTime, executing runnable periodically
*/
private static final ScheduledExecutorService SCHEDULER =
new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder()
.namingPattern("redisLock-schedule-pool")
.daemon(true)
.build());
static {
SCHEDULER.scheduleAtFixedRate(() -> {
// iterate over holderList and extend lock if needed
}, 0, 2, TimeUnit.SECONDS);
}Implementation Details
The core aspect class RedisLockAspect.java contains three parts:
Pointcut definition using @annotation to match @RedisLockAnnotation .
Around advice that parses annotation parameters, attempts to acquire the lock, registers the task in a concurrent queue, proceeds with business logic, and finally releases the lock.
Renewal logic that runs in the scheduled executor, checks each RedisLockDefinitionHolder for expiration, extends the lock, and handles retry limits and thread interruption.
@Around(value = "redisLockPC()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = resolveMethod(pjp);
RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);
String businessKey = ...; // build from annotation and method args
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);
// add task to holderList for renewal
holderList.add(new RedisLockDefinitionHolder(...));
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;
}Testing
A sample controller method demonstrates usage of the annotation with a simulated long‑running task using Thread.sleep . Log output shows lock acquisition, renewal attempts, and eventual interruption when the retry limit is exceeded.
@GetMapping("/testRedisLock")
@RedisLockAnnotation(typeEnum = RedisLockTypeEnum.ONE, lockTime = 3)
public Book testRedisLock(@RequestParam("userId") Long userId) {
try {
log.info("sleep before");
Thread.sleep(10000);
log.info("sleep after");
} catch (Exception e) {
log.info("has some error", e);
}
return null;
}Conclusion
The article summarizes that for time‑consuming business logic and critical data, a distributed Redis lock with automatic renewal prevents duplicate concurrent operations, ensuring data correctness. It also highlights three key concepts learned: AOP usage, scheduled executor services, and thread interruption handling.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.