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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
