Implementing Distributed Locks in Java: Database, Redis, and Zookeeper Solutions

This article explains the concept of distributed locks, compares three common implementation schemes—database unique indexes, Redis SETNX, and Zookeeper temporary sequential nodes—and provides complete Java code examples for each, along with analysis of re‑entrancy, lock release timing, and single‑point failures.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Distributed Locks in Java: Database, Redis, and Zookeeper Solutions

Java's built‑in locks (synchronized and JUC) work only within a single JVM, so in a distributed environment a different mechanism is required; distributed locks solve this problem.

Distributed Lock Implementation Schemes

Three main approaches are covered:

Database‑based lock using a unique index.

Cache‑based lock using Redis (or Memcached, Tair).

Zookeeper‑based lock.

Database‑Based Distributed Lock

The lock relies on a table with a unique index on the unique_mutex column. Inserting a row acquires the lock; a duplicate‑key exception indicates failure.

Table Design

CREATE TABLE `distributed_lock` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `unique_mutex` varchar(255) NOT NULL COMMENT '业务防重id',
  `holder_id` varchar(255) NOT NULL COMMENT '锁持有者id',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `mutex_index` (`unique_mutex`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Lock

insert into distributed_lock(unique_mutex, holder_id) values ('unique_mutex', 'holder_id');

If the insert succeeds the lock is acquired; a DuplicatedKeyException means another competitor already holds the lock.

Unlock

delete from distributed_lock where unique_mutex='unique_mutex' and holder_id='holder_id';

Removing the row releases the lock.

Analysis

The basic scheme is non‑reentrant; to make it re‑entrant you must check that the existing row’s holder_id matches the current requester before inserting. Lock expiration can be handled by comparing the record’s creation time with the current time and deleting stale rows. The database itself can become a single point of failure, so high‑availability solutions (e.g., MySQL MHA) are recommended.

Cache‑Based Distributed Lock (Redis Example)

Redis provides the fastest lock because it operates in memory. The common pattern uses the SETNX command with an expiration ( PX).

Lock

public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    return "OK".equals(result);
}

The command succeeds only when the key does not exist, creating the lock with a timeout.

Unlock

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 Lua script guarantees atomic check‑and‑delete, preventing a client that lost the lock from accidentally unlocking another’s lock.

Analysis

The lock is non‑reentrant; re‑entrancy can be added by checking the stored requestId after a successful SETNX. Expiration avoids deadlocks if a client crashes. High availability is achieved with Redis clusters and master‑slave failover.

Zookeeper‑Based Distributed Lock

Locks are implemented by creating temporary sequential nodes. The client that creates the node with the smallest sequence number holds the lock; others set watches on the predecessor node.

Lock & Unlock Flow

Create an EPHEMERAL‑SEQUENTIAL node under a lock path.

If the node has the smallest sequence, the lock is acquired; otherwise watch the previous node.

When the predecessor node disappears, the watch triggers and the client re‑checks.

Unlocking deletes the client’s node, automatically handled if the session ends.

Curator Example

// Acquire lock (supports timeout and re‑entrancy)
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    try {
        return interProcessMutex.acquire(timeout, unit);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return true;
}

// Release lock
public boolean unlock() {
    try {
        interProcessMutex.release();
    } catch (Throwable e) {
        log.error(e.getMessage(), e);
    } finally {
        executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS);
    }
    return true;
}

Analysis

Re‑entrancy can be achieved by storing client identity in the node data and allowing the same client to acquire the lock again. Zookeeper’s ensemble architecture eliminates the single‑point‑of‑failure issue.

Comparison of the Three Schemes

Each approach has trade‑offs: database locks are simple but suffer from latency and single‑point failures; Redis offers high performance with in‑memory speed but requires careful handling of expiration; Zookeeper provides strong consistency and built‑in high availability but involves more complex node management.

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.

BackendconcurrencyZooKeeperdistributed-lock
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.