Distributed Lock Implementations in Java with Redis and ZooKeeper

Redis-Based Distributed Locking with Redisson

Distributed systems often require mechanisms to ensure that only one process or thread can access a shared resource at a time across different service instances. Redis, a popular in-memory data store, can be effectively leveraged for this purpose, particularly through libraries like Redisson.

Maven Dependency

To integrate Redisson into your Java project, include the following dependency in your pom.xml:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.6</version>
</dependency>

Code Example

The following example demonstrates how to implement a distributed lock using Redisson, showcasing non-blocking acquisition and proper release of resources.

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

public class RedissonLockService {

    private static final String REDIS_ADDRESS = "redis://127.0.0.1:6379";
    private static final String LOCK_KEY = "sharedResourceAccessLock";

    public static void main(String[] args) {
        // Configure the Redisson client for a single Redis server
        Config redissonConfig = new Config();
        redissonConfig.useSingleServer().setAddress(REDIS_ADDRESS);

        // Initialize Redisson client
        RedissonClient redisClient = Redisson.create(redissonConfig);
        RLock distributedLock = redisClient.getLock(LOCK_KEY);

        try {
            // Attempt to acquire the lock with a wait time of 5 seconds
            // and an auto-release time (lease time) of 10 seconds.
            boolean isAcquired = distributedLock.tryLock(5, 10, TimeUnit.SECONDS);

            if (isAcquired) {
                System.out.println("Lock successfully acquired by: " + Thread.currentThread().getName());
                // Execute the critical section logic
                performCriticalOperation();
            } else {
                System.out.println("Failed to acquire lock by: " + Thread.currentThread().getName());
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // Restore the interrupted status
            System.err.println("Lock acquisition interrupted: " + e.getMessage());
        } finally {
            // Ensure the lock is released if held by the current thread
            if (distributedLock.isHeldByCurrentThread()) {
                distributedLock.unlock();
                System.out.println("Lock released by: " + Thread.currentThread().getName());
            }
            // Shut down the Redisson client to free resources
            redisClient.shutdown();
        }
    }

    private static void performCriticalOperation() throws InterruptedException {
        System.out.println("Executing critical section logic...");
        // Simulate a time-consuming operation
        Thread.sleep(3000);
        System.out.println("Critical section logic completed.");
    }
}

This example showcases tryLock for attempting to acquire a lock without blocking indefinitely. It specifies both a wait time for acquisition and a lease time for the lock's validity, preventing deadlocks if a client fails to release the lock. It's crucial to release the lock in a finally block and shut down the Redisson client to prevent resource leaks.

ZooKeeper-Based Distributed Locking with Apache Curator

Apache ZooKeeper provides a robust, highly available coordination service that is well-suited for implementing distributed locks. The Apache Curator framework simplifies interaction with ZooKeeper, offering a higher-level API for various recipes, including distributed locks.

Maven Dependencies

Add the following Curator dependencies to your pom.xml to utilize its distributed lock features:

<dependencies>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>5.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>5.1.0</version>
    </dependency>
</dependencies>

Code Example

Here's an example demonstrating a ZooKeeper-based distributed lock using Curator's InterProcessMutex:

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;

import java.io.Closeable;

public class ZkLockingCoordinator implements Closeable {

    private final CuratorFramework curatorClient;
    private final InterProcessMutex resourceMutex;
    private static final String ZK_CONNECTION_STRING = "localhost:2181";
    private static final String MUTEX_PATH = "/distributed_application_mutex";

    public ZkLockingCoordinator() {
        // Initialize Curator client with a robust retry policy
        curatorClient = CuratorFrameworkFactory.builder()
                .connectString(ZK_CONNECTION_STRING)
                .retryPolicy(new ExponentialBackoffRetry(1000, 5)) // Base sleep time 1s, max 5 retries
                .build();

        // Start the client connection
        curatorClient.start();

        // Create an InterProcessMutex instance for the specified path
        resourceMutex = new InterProcessMutex(curatorClient, MUTEX_PATH);
    }

    public void obtainDistributedLock() throws Exception {
        System.out.println(Thread.currentThread().getName() + " attempting to acquire lock...");
        resourceMutex.acquire(); // Blocks until the lock is acquired
        System.out.println(Thread.currentThread().getName() + " acquired the lock.");
    }

    public void releaseDistributedLock() throws Exception {
        resourceMutex.release();
        System.out.println(Thread.currentThread().getName() + " released the lock.");
    }

    public void executeProtectedOperation() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " performing protected operation.");
        Thread.sleep(2500); // Simulate work
        System.out.println(Thread.currentThread().getName() + " finished protected operation.");
    }

    @Override
    public void close() {
        // Close resources gracefully
        CloseableUtils.closeQuietly(resourceMutex); // No-op if not a Closeable
        CloseableUtils.closeQuietly(curatorClient);
        System.out.println("Curator client and mutex resources closed.");
    }

    public static void main(String[] args) {
        try (ZkLockingCoordinator coordinator = new new ZkLockingCoordinator()) {
            coordinator.obtainDistributedLock();
            coordinator.executeProtectedOperation();
        } catch (Exception e) {
            System.err.println("An error occurred during lock operation: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

The ZkLockingCoordinator class encapsulates the Curator client and the InterProcessMutex. The acquire() method blocks until the lock is obtained, while release() frees it. Using a try-with-resources statement ensures that the client and mutex resources are properly closed, even in case of exceptions. In a production environment, remember to replace localhost:2181 with your actual ZooKeeper ensemble connection string.

Comparative Analysis of Redis and ZooKeeper Distributed Locks

Both Redis and ZooKeeper offer robust solutions for distributed locking, but they differ significantly in their underlying mechanisms, consistency models, and suitability for various use cases.

Comparison Metric Redis Distributed Lock (e.g., Redisson) ZooKeeper Distributed Lock (e.g., Curator)
Core Mechanism Utilizes atomic commands like SET key value NX PX milliseconds (or similar Lua scripts) to acquire locks, leveraging Redis's single-threaded nature for atomicity. Locks are time-based with an expiration. Relies on ZooKeeper's ephemeral sequential nodes. A client attempts to create an ephemeral, sequential node; if it's the smallest sequence number, it holds the lock. Others wait by setting watches on the node preceding theirs.
Consistency Model Primarily offers eventual consistency in a clustered setup. A single Redis instance provides strong consistency, but high-availability setups (like Sentinel or Cluster) can introduce scenarios where a lock might be lost during failovers (unless using Redlock, which adds complexity). Provides strong consistency (linearizability) across its ensemble. ZooKeeper guarantees that operations are processed in order and that all clients see the same state, even during network partitions, ensuring a robust lock owner.
High Availability & Fault Tolerance Achieves high availability through replication (e.g., Sentinel, Cluster). However, a client holding a lock might crash, causing the lock to be held until its lease expires. Redisson includes watchdogs to extend lease time if a client is active. Inherently highly available through its ensemble architecture (odd number of nodes). If a client holding a lock crashes, the ephemeral node associated with the lock is automatically removed, releasing the lock promptly.
Performance Characteristics Generally offers lower latency for lock acquisition and release due to its in-memory nature and simpler network interaction (direct key-value operations). May exhibit slightly higher latency due to the overhead of maintaining strong consistency, sequential node creation, and watch notifications across the ensemble.
Resource Consumption (Client Side) Lock acquisition often involves polling or retry mechanisms if the lock is not immediately available, which can consume CPU cycles if not implemented efficiently (though Redisson's tryLock optimizes this). Clients waiting for a lock register a watch on the preceding node. They are notified only when the lock becomes available, reducing busy-waiting and conserving client-side CPU resources.
Ideal Use Cases Suitable for scenarios where high throughput is critical, and a slight risk of consistency issues (e.g., during specific failover events) is acceptable, such as caching, rate limiting, or idempotent operations. Preferred for environments demanding strict consistency, coordinating critical services, leader election, configuration management, and robust distributed task scheduling.

Insights into Distributed Lock Design

When selecting a distributed locking mechanism, several factors beyond raw performance or ease of implementation should be considered. The fundamental difference in how Redis and ZooKeeper manage client state and consistency leads to distinct reliability profiles.

Redis-based locks, by their nature, rely on a time-based lease. If a client holding a lock crashes before explicitly releasing it, the lock will eventually expire, freeing the resource. While Redisson's watchdog mechanism helps prevent premature expiration for active clients, a hard crash can still leave a lock in limbo until its timeout, potentially delaying other processes.

In contrast, ZooKeeper's design, using ephemeral nodes, offers a more robust solution against client failures. When a client acquires a lock by creating an ephemeral node, that node exists only as long as the client's session with ZooKeeper is active. If the client crashes or its network connection is severed, the session expires, and ZooKeeper automatically deletes all ephemeral nodes associated with that session, including the lock. This mechanism ensures that locks are automatically released upon client failure, minimizing potential deadlocks.

Furthermore, the acquisition model differs. Redis implementations often involve clients repeatedly attempting to acquire a lock or using polling. While libraries like Redisson abstract this complexity, the underlying principle often involves an active waiting mechenism. ZooKeeper, with its watch mechanism, allows clients to passively wait for a notification when the lock becomes available, potentially leading to more efficient resource utilization on the client side by avoiding constant polling.

Tags: java Distributed Systems Redis ZooKeeper Redisson

Posted on Sun, 07 Jun 2026 18:00:34 +0000 by Pilly