Panel For Example Panel For Example Panel For Example

Redis Distributed Locks: Five Approaches

Author : Adrian April 08, 2026

 

1. The problem with local locks

Local locking mechanisms (for example synchronized or Lock in a single JVM) do not work across a distributed cluster. Consider a system split into four microservices behind a front-end gateway. If a high concurrent burst of requests arrives and the cache expires, each microservice may attempt to access the database and apply a local lock to its own threads to prevent cache stampede.

Because local locks only protect threads inside one process, several services may concurrently perform database reads and update cache keys independently, producing inconsistent or unexpected results. For example, service A may update cache key=100 while service B, not constrained by A's local lock, updates key=99. The final state becomes indeterminate and differs from the intended single-writer behavior.

 

 

2. What is a distributed lock

A distributed lock is a mechanism that provides mutual exclusion across processes or nodes in a cluster. Only one thread across the entire distributed system may execute the critical section; other threads must wait until the lock is released.

Analogy: a single door lock outside a room. All concurrent actors try to enter through the door; only one can hold the key and enter. When that actor exits, the lock is released and another can enter.

distributed-lock-analogy

Basic workflow for a distributed lock: all request threads try to "occupy a slot" at a shared location (for example, Redis or a database). The thread that successfully occupies the slot executes the critical business logic, then releases the slot. Other threads wait and retry until they obtain the slot.

distributed-lock-flow

 

3. Redis and SETNX

Redis, as a shared accessible store, can act as the lock slot. Many simple Redis-based lock implementations use SETNX (set if not exists) as the primitive. Higher-level schemes differ in additional parameters and handling of edge cases.

SETNX sets a key only when the key does not already exist.

set <key> <value> NX

Example: try SETNX from a Redis container.

docker exec -it <container id> redis-cli

set wukong 1111 NX

If the command returns OK, the key was set. Repeating the command returns nil, indicating failure to set because the key already exists.

 

 

4. Bronze approach (simple SETNX)

4.1 Principle

Multiple concurrent threads attempt to acquire the lock by calling SETNX. The thread that succeeds holds the lock. After finishing its work, that thread deletes the lock. Other threads periodically retry until they obtain the lock.

 

4.2 Example code

// 1. Try to acquire lock Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123"); if (lock) { // 2. Acquired lock successfully, execute business logic List<typeentity> typeEntityListFromDb = getDataFromDB(); // 3. Unlock redisTemplate.delete("lock"); return typeEntityListFromDb; } else { // 4. Sleep for a while sleep(100); // 5. Failed to acquire lock, wait and retry return getTypeEntityListByRedisDistributedLock(); }

4.3 Bronze scheme drawback

If the thread that holds the lock crashes or throws an exception before deleting the lock, the key remains in Redis and causes a deadlock, preventing other threads from acquiring the lock.

Mitigation: set an automatic expiration on the lock so it is deleted after a timeout.

 

5. Silver approach (SETNX + expire)

5.1 Analogy

Attach a sand-timer to the lock: after the timer expires the lock automatically opens. This prevents permanent deadlock if the owner crashes.

silver-analogy

5.2 Principle

After successfully setting the lock, set an expiration time on the key. Note that in some implementations these are separate steps.

5.3 Example code

// Set lock to be automatically cleared after 10s redisTemplate.expire("lock", 10, TimeUnit.SECONDS);

// 1. Try to acquire lock Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123"); if (lock) { // 2. Set automatic expiry after 10s redisTemplate.expire("lock", 10, TimeUnit.SECONDS); // 3. Acquired lock, execute business logic List<typeentity> typeEntityListFromDb = getDataFromDB(); // 4. Unlock redisTemplate.delete("lock"); return typeEntityListFromDb; }

5.4 Silver scheme drawback

Because setting the lock and setting its expiry are two separate operations, if a failure occurs between them the expiry may never be set. The lock can still become permanent, similar to the bronze scheme.

 

6. Gold approach (atomic SET with expiry)

6.1 Atomic command

To ensure atomicity, perform the set-and-expire in a single atomic operation. Redis supports this with the SET command options.

# Set a key with expiration (milliseconds or seconds) only if it does not exist: set <key> <value> PX <milliseconds> NX or set <key> <value> EX <seconds> NX

Check remaining TTL with:

ttl <key>

Example: set key=wukong, value=1111, expiry=5000ms

set wukong 1111 PX 5000 NX # then ttl wukong

 

6.2 Principle

The gold scheme acquires the lock and sets its expiration in one atomic operation, so either both succeed or neither does.

gold-flow

6.3 Example code

setIfAbsent("lock", "123", 10, TimeUnit.SECONDS);

6.4 Gold scheme drawback

A different issue arises when multiple clients set the same lock value. If the lock value is identical across clients, a client may accidentally delete another client's lock after its own lock expires and the other client acquires the lock using the same value.

 

7. Platinum approach (unique lock value)

7.1 Analogy

Give each lock owner a different identifier so that clients do not remove locks that they do not own.

platinum-static-illustration

7.2 Principle

When acquiring the lock, set the key value to a unique identifier. When releasing, compare the current key value with the identifier; only delete if they match.

platinum-flow

7.3 Example code

// 1. Generate unique id String uuid = UUID.randomUUID().toString(); // 2. Try to acquire lock with unique id and 10s expiry Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS); if (lock) { System.out.println("Acquired lock: " + uuid); // 3. Acquired lock, execute business logic List<typeentity> typeEntityListFromDb = getDataFromDB(); // 4. Get current lock value String lockValue = redisTemplate.opsForValue().get("lock"); // 5. If lock value equals our id, delete our lock if (uuid.equals(lockValue)) { System.out.println("Clearing lock: " + lockValue); redisTemplate.delete("lock"); } return typeEntityListFromDb; } else { System.out.println("Failed to acquire lock, waiting for release"); // 4. Sleep for a while sleep(100); // 5. Failed to acquire lock, wait and retry return getTypeEntityListByRedisDistributedLock(); }

7.4 Platinum scheme drawback

The check-and-delete sequence (get current value, compare, then delete) is not atomic. A race can occur if the lock expires and another client acquires it between the get and delete steps, causing a client to delete a lock owned by someone else.

platinum-race-condition-diagram

 

8. Diamond approach (atomic check-and-delete via Lua)

Make the check-and-delete operation atomic by executing both steps inside Redis as a script. Redis executes Lua scripts atomically.

diamond-flow

8.1 Lua script

if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

The script compares the key value with the provided identifier and deletes the key only if they match. This ensures atomicity.

8.2 Execute the script from Java

// Script to unlock String script = "if redis.call('get',KEYS[1]) == ARGV[1] then " + "return redis.call('del',KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);

Because the Lua script runs inside Redis, the get-and-delete is atomic and avoids the race described in the platinum scheme.

 

9. Summary

This article progressed from the limitations of local locks to five Redis-based distributed lock approaches, showing how each evolution addresses specific failure modes:

  • Bronze: Simple SETNX. Drawback: if the owner crashes before releasing, the lock can remain forever. Fix: add expiry.
  • Silver: SETNX then set expiry. Drawback: these are two separate operations; a failure between them can still leave a permanent lock. Fix: make set-and-expiry atomic.
  • Gold: SET with EX/PX and NX options to set-and-expire atomically. Drawback: if all clients use the same lock value, a client can delete another's lock after expiry.
  • Platinum: Use a unique identifier per lock owner and compare before deleting. Drawback: the compare-and-delete is not atomic and can race with lock expiry and re-acquisition.
  • Diamond: Use a Redis Lua script to perform the compare-and-delete atomically. This addresses the race in the platinum scheme.

For more robust and feature-rich distributed locks, consider mature libraries that implement these best practices and additional safety measures.

lock-evolution-diagram