Implementing Distributed Locks with Redis, Redisson, and Zookeeper in Java

This article explains the principles and practical implementations of distributed locks using Redis (SETNX and SET with expiration), Redisson's Java client, and Zookeeper's sequential ephemeral nodes, providing code examples, lock acquisition and release mechanisms, and a comparison of their advantages and drawbacks.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Distributed Locks with Redis, Redisson, and Zookeeper in Java

In modern clustered deployments, local locks become insufficient, so distributed locks are needed to ensure that only one service instance can execute a critical section at a time.

Redis Implementation

Redis provides the SETNX command (set if not exists) and the combined SET key value EX seconds NX command to acquire a lock atomically with an expiration time, preventing deadlocks caused by process crashes.

private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";

@Override
public String acquire() {
    try {
        long end = System.currentTimeMillis() + acquireTimeout;
        String requireToken = UUID.randomUUID().toString();
        while (System.currentTimeMillis() < end) {
            String result = jedis.set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                return requireToken;
            }
            try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
    } catch (Exception e) {
        log.error("acquire lock due to error", e);
    }
    return null;
}

@Override
public boolean release(String identify) {
    if (identify == null) { return false; }
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    try {
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(identify));
        if (RELEASE_SUCCESS.equals(result)) {
            log.info("release lock success, requestToken:{}", identify);
            return true;
        }
    } catch (Exception e) {
        log.error("release lock due to error", e);
    } finally {
        if (jedis != null) { jedis.close(); }
    }
    log.info("release lock failed, requestToken:{}, result:{}", identify, result);
    return false;
}

The Lua script guarantees atomic check‑and‑delete when releasing the lock.

Redisson Implementation

Redisson wraps Redis and offers a Java API similar to java.util.concurrent.locks.Lock. After adding the Redisson dependency, a lock can be obtained and used as follows:

private void test() {
    RLock lock = redissonClient.getLock("test_lock");
    lock.lock();
    try {
        // business logic
    } finally {
        lock.unlock();
    }
}

Redisson also supports read‑write locks, fair locks, and the RedLock algorithm. By default it provides a watchdog that automatically renews the lock every 10 seconds, keeping the lock alive as long as the holder is active.

Zookeeper Implementation

Zookeeper can be used to create temporary sequential nodes; the smallest node holds the lock. Other clients watch the predecessor node and acquire the lock when it disappears, avoiding the “herd effect”.

public class ZooKeeperDistributedLock implements Watcher {
    private ZooKeeper zk;
    private String locksRoot = "/locks";
    private String productId;
    private String waitNode;
    private String lockNode;
    private CountDownLatch latch;
    private CountDownLatch connectedLatch = new CountDownLatch(1);
    private int sessionTimeout = 30000;

    public ZooKeeperDistributedLock(String productId) {
        this.productId = productId;
        try {
            String address = "192.168.189.131:2181,192.168.189.132:2181";
            zk = new ZooKeeper(address, sessionTimeout, this);
            connectedLatch.await();
        } catch (Exception e) { throw new LockException(e); }
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getState() == KeeperState.SyncConnected) { connectedLatch.countDown(); return; }
        if (this.latch != null) { this.latch.countDown(); }
    }

    public void acquireDistributedLock() {
        try {
            if (this.tryLock()) { return; }
            else { waitForLock(waitNode, sessionTimeout); }
        } catch (Exception e) { throw new LockException(e); }
    }

    public boolean tryLock() {
        try {
            lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            List<String> locks = zk.getChildren(locksRoot, false);
            Collections.sort(locks);
            if (lockNode.equals(locksRoot + "/" + locks.get(0))) { return true; }
            int previousLockIndex = -1;
            for (int i = 0; i < locks.size(); i++) {
                if (lockNode.equals(locksRoot + "/" + locks.get(i))) { previousLockIndex = i - 1; break; }
            }
            this.waitNode = locks.get(previousLockIndex);
        } catch (Exception e) { throw new LockException(e); }
        return false;
    }

    private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException {
        Stat stat = zk.exists(locksRoot + "/" + waitNode, true);
        if (stat != null) {
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, TimeUnit.MILLISECONDS);
            this.latch = null;
        }
        return true;
    }

    public void unlock() {
        try {
            System.out.println("unlock " + lockNode);
            zk.delete(lockNode, -1);
            lockNode = null;
            zk.close();
        } catch (Exception e) { e.printStackTrace(); }
    }

    public static class LockException extends RuntimeException {
        public LockException(String e) { super(e); }
        public LockException(Exception e) { super(e); }
    }
}

Summary and Comparison

Redis implements a lock by inserting a key with an expiration; Zookeeper uses a temporary sequential node.

When a server crashes, Redis relies on key expiration, while Zookeeper automatically removes the temporary node.

Redis clients often spin‑wait for the lock, which can waste CPU; Zookeeper clients use watchers, offering better performance under contention.

The choice between Redis and Zookeeper should depend on the specific project’s technology stack, latency requirements, and failure‑handling strategy.

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.

JavaconcurrencyredisZooKeeperredisson
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

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.