Implementing Distributed Redis Locks in Spring Boot: From Synchronized Locks to Redisson

The article explains how to protect a flash‑sale inventory using Redis locks in a Spring Boot service, starting with a simple synchronized lock, moving to distributed locks with setIfAbsent, handling lock expiration issues, extending TTL, and finally simplifying the implementation with Redisson.

Top Architect
Top Architect
Top Architect
Implementing Distributed Redis Locks in Spring Boot: From Synchronized Locks to Redisson

A senior architect presents a step‑by‑step guide for handling product flash‑sale concurrency in a Spring Boot online store by using Redis as a distributed lock provider.

1. Single‑machine lock

The inventory count is stored in Redis; each request checks the count and, within a synchronized block, decrements it to ensure atomicity.

package springbootdemo.demo.controller;
/*
 * @auther 顶风少年
 * @mail [email protected]
 * @date 2020-01-13 11:19
 * @notify
 * @version 1.0
 */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        synchronized (this) {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("抢到了" + count + "号商品");
            }
            return "";
        }
    }
}

2. Distributed lock with Redis setIfAbsent

When the system is scaled to multiple instances behind Nginx, the synchronized block no longer works. A lock key is created with setIfAbsent; if the lock cannot be obtained, the request returns immediately.

package springbootdemo.demo.controller;
/* same header omitted */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "");
        if (!phoneLock) {
            return "";
        }
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("抢到了" + count + "号商品");
            }
        } finally {
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

3. Handling lock expiration

If a service crashes while holding the lock, the lock may never be released. Adding an expiration time to the lock key mitigates this, but a thread that still holds the lock after expiration may incorrectly release another thread's lock.

package springbootdemo.demo.controller;
/* same header omitted */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;

@RestController
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
        if (!phoneLock) {
            return "";
        }
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                // simulate long processing
                Thread.sleep(99999999999L);
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("抢到了" + count + "号商品");
            }
        } finally {
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

4. Extending lock TTL

To avoid the lock expiring while the business logic is still running, a timer repeatedly refreshes the lock's TTL until the transaction finishes.

package springbootdemo.demo.controller;
/* same header omitted */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@RestController
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "buy")
    public String get() {
        Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
        if (!phoneLock) {
            return "";
        }
        Timer timer = null;
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                timer = new Timer();
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        redisTemplate.opsForValue().set("phoneLock", "", 3, TimeUnit.SECONDS);
                    }
                }, 0, 1);
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("抢到了" + count + "号商品");
            }
        } finally {
            if (timer != null) {
                timer.cancel();
            }
            redisTemplate.delete("phoneLock");
        }
        return "";
    }
}

5. Simplifying with Redisson

Redisson provides a high‑level RLock API that automatically handles lock acquisition, expiration renewal, and release, removing the need for manual TTL management.

package springbootdemo.demo.controller;
/* same header omitted */
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;

@RestController
public class RedissonLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private Redisson redisson;

    @GetMapping(value = "buy2")
    public String get() {
        RLock phoneLock = redisson.getLock("phoneLock");
        phoneLock.lock(3, TimeUnit.SECONDS);
        try {
            String phone = redisTemplate.opsForValue().get("phone");
            Integer count = Integer.valueOf(phone);
            if (count > 0) {
                redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
                System.out.println("抢到了" + count + "号商品");
            }
        } finally {
            phoneLock.unlock();
        }
        return "";
    }
}

The progression demonstrates how a simple synchronized block evolves into a robust distributed lock solution, ending with a concise Redisson implementation that solves the common pitfalls of lock expiration and renewal.

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.

Backend DevelopmentconcurrencyredisSpring Bootredisson
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.