Master Redis Distributed Locks in Java: Correct Implementation & Common Pitfalls

This article explains how to correctly implement a Redis-based distributed lock in Java, covering essential reliability requirements, step‑by‑step code for acquiring and releasing locks, and detailed analyses of common flawed approaches, ensuring atomicity and proper ownership handling.

ITFLY8 Architecture Home
ITFLY8 Architecture Home
ITFLY8 Architecture Home
Master Redis Distributed Locks in Java: Correct Implementation & Common Pitfalls

Introduction

Distributed locks can be implemented in three main ways: database optimistic lock, Redis‑based lock, and ZooKeeper‑based lock. This article focuses on the second method, providing a correct Redis implementation and pointing out common mistakes found in many online examples.

Reliability Requirements

To ensure a distributed lock is reliable, it must satisfy four conditions:

Mutual exclusion : Only one client can hold the lock at any time.

No deadlock : If a client crashes while holding the lock, other clients must still be able to acquire it later.

Fault tolerance : The lock should work as long as a majority of Redis nodes are operational.

Owner awareness : The same client that acquired the lock must be the one to release it.

Code Implementation

Component Dependency

Add the Jedis library to your Maven pom.xml:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

Lock Acquisition

The correct way to acquire a lock uses a single atomic SET command with the NX and PX options:

public class RedisTool {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * Try to acquire a distributed lock.
     * @param jedis Redis client
     * @param lockKey Lock key
     * @param requestId Unique identifier of the requester
     * @param expireTime Expiration time in milliseconds
     * @return true if the lock was acquired
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

Explanation of the five parameters of jedis.set:

key : the lock identifier; it must be unique.

value : a requestId (e.g., UUID.randomUUID().toString()) that identifies the client that owns the lock.

NX : "SET IF NOT EXIST" – the command succeeds only when the key does not already exist, guaranteeing mutual exclusion.

PX : sets an expiration time for the key.

expireTime : the expiration duration in milliseconds.

Using this atomic command satisfies mutual exclusion, prevents deadlocks (the key expires automatically), and records the owner for safe release.

Common Mistake 1 – Non‑Atomic setnx + expire

public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {
    Long result = jedis.setnx(lockKey, requestId);
    if (result == 1) {
        // If the process crashes here, the lock has no expiration → deadlock
        jedis.expire(lockKey, expireTime);
    }
}

This approach is not atomic; a crash between setnx and expire leaves the lock without a timeout.

Common Mistake 2 – Complex setnx with client‑generated expiration

public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {
    long expires = System.currentTimeMillis() + expireTime;
    String expiresStr = String.valueOf(expires);
    if (jedis.setnx(lockKey, expiresStr) == 1) {
        return true;
    }
    String currentValueStr = jedis.get(lockKey);
    if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
        String oldValueStr = jedis.getSet(lockKey, expiresStr);
        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
            return true;
        }
    }
    return false;
}

Problems:

Clients must have synchronized clocks.

When multiple clients set the expiration simultaneously, one may overwrite another’s timeout.

The lock lacks an owner identifier, allowing any client to release it.

Unlock Code

Correct Unlock Using Lua

A Lua script guarantees atomic check‑and‑delete:

public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    return Long.valueOf(1L).equals(result);
}

The script returns 1 only when the stored value matches requestId, ensuring that only the lock owner can delete it.

Common Unlock Mistake 1 – Direct delete

public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
    jedis.del(lockKey);
}

Any client can delete the lock, even if it does not own it.

Common Unlock Mistake 2 – Separate get and del

public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
    if (requestId.equals(jedis.get(lockKey))) {
        // Race condition: the lock may have been acquired by another client after the get
        jedis.del(lockKey);
    }
}

If the lock expires after the get but before the del, a client may unintentionally delete another client’s lock.

Conclusion

This article demonstrated a reliable Java implementation of a Redis distributed lock, highlighted four essential reliability criteria, and presented two classic error patterns for both locking and unlocking. By using the atomic SET command with NX and PX, and a Lua script for release, developers can avoid deadlocks, ensure ownership, and achieve fault‑tolerant locking.

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.

concurrencyredisJedisdistributed-lockLua
ITFLY8 Architecture Home
Written by

ITFLY8 Architecture Home

ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.

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.