Backend Development 14 min read

Understanding Distributed Locks and Robust Implementations with Redis

This article explains the challenges of thread synchronization in distributed systems, introduces the concept of distributed locks, compares common implementations such as Redis, Zookeeper, and databases, and provides robust Java and Lua solutions to ensure atomicity, avoid deadlocks, and support lock renewal.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Distributed Locks and Robust Implementations with Redis

As system architectures shift from single‑machine to distributed environments, guaranteeing thread synchronization across nodes becomes critical; distributed locks provide the essential mechanism for coordinating shared resources and tasks.

1. Overview of Distributed Locks

Local locks like synchronized and ReentrantLock work only within a single JVM. In a multi‑node deployment, these mechanisms fail to prevent concurrent access to the same resource, necessitating a lock that spans nodes.

What a Distributed Lock Does

A distributed lock ensures that at any moment only one node or thread can hold a given resource, typically implemented by external services such as Redis, Zookeeper, or Tair, which remain consistent even during node failures or network partitions.

2. Common Implementation Approaches

Redis‑based lock: Uses SETNX (or SET … NX EX ) to acquire a lock with an expiration time, preventing deadlocks.

Zookeeper‑based lock: Creates temporary sequential nodes to achieve strong consistency, suitable for distributed transactions.

Database‑based lock: Leverages row‑level locks via insert/update operations; simple but lower performance.

Tair‑based lock: High‑performance cache offering lock capabilities for high‑concurrency scenarios.

3. Basic Redis Lock (Naïve Implementation)

The following Java‑style pseudo‑code demonstrates a simple lock acquisition, execution, and release using Redis:

// Attempt to acquire lock
if (set(key, 1, "NX", "EX", 30)) {
    try {
        // Execute protected business logic
    } finally {
        // Release lock
        del(key);
    }
}

While straightforward, this approach suffers from non‑atomic lock acquisition, premature expiration, and lack of re‑entrancy.

Problems with the Naïve Approach

Non‑atomic acquisition and TTL setting can cause deadlocks if the process crashes after SETNX but before EXPIRE .

Expired locks may be deleted by a thread that no longer owns them, leading to data inconsistency.

No support for re‑entrancy; recursive calls fail to reacquire the lock.

4. Robust Distributed Lock Design

4.1 Storing a Unique Identifier

Generate a UUID (or thread ID) as the lock value, ensuring that only the owner can release the lock.

String threadId = UUID.randomUUID().toString(); // generate unique identifier
if (set(key, threadId, "NX", "EX", 30)) {
    try {
        // business logic
    } finally {
        if (threadId.equals(get(key))) {
            del(key); // release only if still owner
        }
    }
}

4.2 Ensuring Atomicity with Lua Scripts

Combine SETNX and EXPIRE into a single atomic operation using a Lua script:

if (redis.call('setnx', KEYS[1], ARGV[1]) < 1) then
    return 0; -- lock acquisition failed
end
redis.call('expire', KEYS[1], tonumber(ARGV[2]));
return 1; -- lock acquired

Release the lock atomically by checking the stored identifier:

if (redis.call('get', KEYS[1]) == ARGV[1]) then
    return redis.call('del', KEYS[1]); -- release lock
end
return 0; -- not the lock owner

Java can execute these scripts via jedis.eval to guarantee atomicity.

4.3 Automatic Lock Renewal (Watchdog)

Start a guardian thread that periodically extends the TTL before it expires, preventing premature unlocks caused by long‑running tasks.

// Acquire lock and start watchdog
if (set(key, threadId, "NX", "EX", 30)) {
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    scheduler.scheduleAtFixedRate(() -> {
        if (threadId.equals(get(key))) {
            expire(key, 30); // renew lock
        }
    }, 25, 25, TimeUnit.SECONDS);
    try {
        // business logic
    } finally {
        if (threadId.equals(get(key))) {
            del(key);
        }
        scheduler.shutdown();
    }
}

5. Summary

Distributed locks are essential for ensuring mutual exclusion across multiple nodes, with typical implementations relying on external services like Redis or Zookeeper. A robust lock should provide mutual exclusion, re‑entrancy, atomic operations, timeout handling, and optional watchdog renewal to maintain reliability in production distributed systems.

Key Characteristics

Mutual exclusion

Re‑entrancy support

Atomic acquisition and release

Configurable TTL and renewal mechanisms

High performance and high availability

JavaConcurrencyRedisDistributed LockluaAtomicitywatchdog
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.