Implementing Distributed Locks in Spring Boot with Redis

This article explains why local locks like synchronized and ReentrantLock fail in microservice clusters, introduces the fundamentals and essential properties of distributed locks, walks through five iterative Redis lock implementations that address dead‑lock, atomicity, and timeout issues, and finally shows how to adopt the production‑grade Redisson library with best‑practice guidelines for key naming, lock scope, and lock type selection.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Implementing Distributed Locks in Spring Boot with Redis

1. Why Local Locks Are Insufficient

In a monolithic JVM, synchronized and ReentrantLock can serialize threads, but in a microservice cluster each instance runs in its own JVM, so these locks cannot coordinate across machines. This leads to problems such as overselling, duplicate orders, repeated scheduled tasks, and data overwrites.

2. Distributed Lock Basics

A distributed lock must provide mutual exclusion across processes, machines, and service instances. The four core properties are:

Mutual exclusion : only one client may hold the lock at any time.

Dead‑lock prevention : the lock must be released automatically on crashes or network failures.

Atomicity : lock acquisition and release must be a single atomic operation.

Re‑entrancy : the same thread can acquire the same lock multiple times without self‑dead‑lock.

3. Redis as the Lock Provider

Redis executes commands in a single‑threaded, sequential manner, guaranteeing atomicity and ordering. The lock is implemented by setting a unique key; success of the SET NX (or setIfAbsent) command means the lock is acquired, and deleting the key releases it.

4. Iterative Redis Lock Implementations

V1. Basic Lock (no expiration)

@RestController
public class LockController {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private static final String LOCK_KEY = "lock:stock";

    @GetMapping("/stock/deduct/v1")
    public String deductStockV1() {
        Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "lock");
        if (!Boolean.TRUE.equals(lockResult)) {
            return "系统繁忙,请勿重复操作";
        }
        try {
            System.out.println("执行库存扣减业务逻辑");
            return "操作成功";
        } finally {
            redisTemplate.delete(LOCK_KEY);
        }
    }
}

Defect: No expiration; if the service crashes, the key remains forever, causing a permanent dead‑lock.

V2. Add Fixed Expiration

@GetMapping("/stock/deduct/v2")
public String deductStockV2() {
    Boolean lockResult = redisTemplate.opsForValue()
        .setIfAbsent(LOCK_KEY, "lock", 10, TimeUnit.SECONDS);
    if (!Boolean.TRUE.equals(lockResult)) {
        return "系统繁忙,请勿重复操作";
    }
    try {
        System.out.println("执行库存扣减业务逻辑");
        return "操作成功";
    } finally {
        redisTemplate.delete(LOCK_KEY);
    }
}

Defect: Setting the expiration is a separate step; if the service crashes after acquiring the lock but before setting the TTL, the lock can remain indefinitely.

V3. Atomic SET NX EX + UUID Guard

@GetMapping("/stock/deduct/v3")
public String deductStockV3() {
    String lockValue = UUID.randomUUID().toString();
    Boolean lockResult = redisTemplate.opsForValue()
        .setIfAbsent(LOCK_KEY, lockValue, 10, TimeUnit.SECONDS);
    if (!Boolean.TRUE.equals(lockResult)) {
        return "系统繁忙,请勿重复操作";
    }
    try {
        Thread.sleep(6000);
        System.out.println("执行库存扣减业务逻辑");
        return "操作成功";
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return "业务执行异常";
    } finally {
        String currentValue = (String) redisTemplate.opsForValue().get(LOCK_KEY);
        if (lockValue.equals(currentValue)) {
            redisTemplate.delete(LOCK_KEY);
        }
    }
}

Defect: Unlock consists of a read‑check then delete, which is not atomic; under high contention a thread may delete a lock that another thread has just acquired.

V4. Lua Script for Atomic Unlock

@GetMapping("/stock/deduct/v4")
public String deductStockV4() {
    String lockValue = UUID.randomUUID().toString();
    Boolean lockResult = redisTemplate.opsForValue()
        .setIfAbsent(LOCK_KEY, lockValue, 10, TimeUnit.SECONDS);
    if (!Boolean.TRUE.equals(lockResult)) {
        return "系统繁忙,请勿重复操作";
    }
    try {
        Thread.sleep(6000);
        System.out.println("执行库存扣减业务逻辑");
        return "操作成功";
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return "业务执行异常";
    } finally {
        String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        redisTemplate.execute(script, Collections.singletonList(LOCK_KEY), lockValue);
    }
}

Defect: If business execution exceeds the 10‑second TTL, the lock expires and another thread may acquire it while the original thread is still running.

V5. Manual Watchdog (Renewal) Mechanism

Starts an asynchronous thread that periodically checks the remaining TTL; if the lock is about to expire and the business is still running, it extends the TTL. This mimics Redisson’s built‑in watchdog but is complex and error‑prone, so the article advises using Redisson in production.

5. Redisson – Production‑Grade Distributed Lock

5.1 Dependency

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.7</version>
</dependency>

5.2 Global Configuration

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://127.0.0.1:6379")
            .setDatabase(0);
        return Redisson.create(config);
    }
}

5.3 Re‑entrant Lock Example

@RestController
public class RedissonLockController {
    @Resource
    private RedissonClient redissonClient;
    private static final String LOCK_KEY = "lock:stock:deduct";

    @GetMapping("/stock/deduct/final")
    public String deductStock() {
        RLock lock = redissonClient.getLock(LOCK_KEY);
        try {
            boolean lockSuccess = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!lockSuccess) {
                return "操作繁忙,请稍后重试";
            }
            System.out.println("Redisson分布式锁执行库存扣减成功");
            return "操作成功";
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return "业务执行异常";
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

6. Redisson Core Capabilities

Watchdog Auto‑Renewal : When no explicit TTL is set, Redisson creates a 30‑second lock and extends it every 10 seconds as long as the owning thread holds the lock.

Re‑entrancy : Implemented via a Redis hash where the field stores the thread ID and the value stores the re‑entry count.

Lock Types : Re‑entrant (default), fair lock, read‑write lock, and RedLock (multi‑node) for high‑availability financial scenarios.

7. Redis Distributed‑Lock Best‑Practice Guidelines

7.1 Key Naming

Use a structured pattern module:scenario:resourceId, e.g., lock:stock:deduct:1001, to avoid collisions and simplify troubleshooting.

7.2 TTL Configuration

For unpredictable business durations, rely on the watchdog; for short, predictable tasks, set the TTL to 2‑3× the maximum expected execution time.

7.3 Acquire‑Release Pattern

Always pair lock acquisition with a finally block that releases the lock after verifying the current thread still holds it.

7.4 Lock Granularity

Lock only the critical section that truly contends for the shared resource; avoid locking an entire endpoint or whole business flow.

7.5 Lock Type Selection

Use the default re‑entrant lock for ordinary updates, fair lock for ordered processing, read‑write lock for read‑heavy scenarios, and RedLock for financial‑grade high‑availability requirements.

7.6 Production Restrictions

Never use hand‑written native Redis locks in production; adopt Redisson, avoid infinite lock‑retry loops, log lock operations, and strictly follow the above conventions to prevent data inconsistency.

Mastering the underlying principles, iterative refinements, and Redisson’s standardized features enables developers to build reliable, high‑concurrency microservices that maintain data consistency across distributed environments.

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.

Javamicroservicesdistributed-lockredisson
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

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.