Mastering Distributed Locks with Redis: From Basic SetNX to Redisson’s Atomic Solutions

This article walks through the evolution of Redis-based distributed locking—from simple SETNX placeholders and manual expiration, through atomic SETNX EX, UUID‑based lock ownership, Lua‑scripted unlocks, and finally Redisson’s high‑level lock API—highlighting common pitfalls and robust solutions for backend developers.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Mastering Distributed Locks with Redis: From Basic SetNX to Redisson’s Atomic Solutions

Hello, I'm Feng Ge.

Distributed Lock Evolution

Basic Principle

We can "reserve a spot" in a shared place; if successful, we execute the logic, otherwise we wait until the lock is released. The reservation can be done in Redis, a database, or any accessible location, and waiting can be implemented via spinning.

Stage One

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {<br>    // Stage one<br>    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");<br>    // If lock acquired, execute business<br>    if (lock) {<br>        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();<br>        // Delete lock; if an exception or crash occurs before this, a deadlock may happen<br>        stringRedisTemplate.delete("lock");<br>        return categoriesDb;<br>    } else {<br>        // No lock, wait 100ms and retry<br>        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }<br>        return getCatalogJsonDbWithRedisLock();<br>    }<br>}<br><br>public Map<String, List<Catalog2Vo>> getCategoryMap() {<br>    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();<br>    String catalogJson = ops.get("catalogJson");<br>    if (StringUtils.isEmpty(catalogJson)) {<br>        System.out.println("Cache miss, querying database…");<br>        Map<String, List<Catalog2Vo>> categoriesDb = getCategoriesDb();<br>        String toJSONString = JSON.toJSONString(categoriesDb);<br>        ops.set("catalogJson", toJSONString);<br>        return categoriesDb;<br>    }<br>    System.out.println("Cache hit…");<br>    Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});<br>    return listMap;<br>}<br>

Problem: setnx reserves the spot, but if business code throws an exception or the process crashes, the lock is never released, causing a deadlock.

Solution: Set an automatic expiration for the lock so it is removed even if delete is not executed.

Stage Two

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {<br>    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");<br>    if (lock) {<br>        // Set expiration time<br>        stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);<br>        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();<br>        stringRedisTemplate.delete("lock");<br>        return categoriesDb;<br>    } else {<br>        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }<br>        return getCatalogJsonDbWithRedisLock();<br>    }<br>}<br>

Problem: setnx succeeds, but before setting the expiration the process crashes, leading to a deadlock.

Solution: The reservation and expiration must be atomic; Redis supports the SET command with NX and EX options.

Stage Three

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {<br>    // Acquire lock and set expiration atomically<br>    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111", 5, TimeUnit.SECONDS);<br>    if (lock) {<br>        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();<br>        // Simulate long business execution<br>        try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); }<br>        stringRedisTemplate.delete("lock");<br>        return categoriesDb;<br>    } else {<br>        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }<br>        return getCatalogJsonDbWithRedisLock();<br>    }<br>}<br>

Problem: Deleting the lock directly can be unsafe if the business takes longer than the lock’s TTL; the lock may expire and be acquired by another process, causing the original process to delete someone else’s lock.

Solution: When acquiring the lock, store a unique UUID as the value and delete the lock only if the stored value matches the UUID.

Stage Four

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {<br>    String uuid = UUID.randomUUID().toString();<br>    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();<br>    Boolean lock = ops.setIfAbsent("lock", uuid, 5, TimeUnit.SECONDS);<br>    if (lock) {<br>        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();<br>        String lockValue = ops.get("lock");<br>        if (lockValue.equals(uuid)) {<br>            try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); }<br>            stringRedisTemplate.delete("lock");<br>        }<br>        return categoriesDb;<br>    } else {<br>        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }<br>        return getCatalogJsonDbWithRedisLock();<br>    }<br>}<br>

Problem: If the lock expires just before the owner deletes it, another process may have already set a new lock, and the delete operation would remove the new lock.

Solution: Deleting the lock must be atomic; use a Redis Lua script to compare the stored value and delete only when they match.

Stage Five – Final Form

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {<br>    String uuid = UUID.randomUUID().toString();<br>    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();<br>    Boolean lock = ops.setIfAbsent("lock", uuid, 5, TimeUnit.SECONDS);<br>    if (lock) {<br>        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();<br>        String lockValue = ops.get("lock");<br>        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then
" +<br>                        "    return redis.call(\"del\",KEYS[1])
" +<br>                        "else
" +<br>                        "    return 0
" +<br>                        "end";<br>        stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);<br>        return categoriesDb;<br>    } else {<br>        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }<br>        return getCatalogJsonDbWithRedisLock();<br>    }<br>}<br>

This ensures atomicity for both acquiring (reservation + expiration) and releasing (check + delete) the lock. The next challenge is automatic lock renewal.

Redisson

Redisson is a Java in‑memory data grid built on top of Redis. It provides distributed implementations of many common Java objects and services, such as BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish/Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, and Scheduler service.

BitSet Set Multimap SortedSet Map List Queue BlockingQueue Deque BlockingDeque Semaphore Lock AtomicLong CountDownLatch Publish/Subscribe Bloom filter Remote service Spring cache Executor service Live Object service Scheduler service

Redisson offers the simplest and most convenient way to use Redis, aiming to separate concerns so developers can focus on business logic.

https://github.com/redisson/redisson/wiki

Source: https://blog.csdn.net/zhangkaixuan456/article/details/110679617

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.

Javaredisdistributed-lockredisson
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.