Understanding Distributed Locks: Java Native Locks, RedissonLock, RedLock, and WLock – Concepts, Algorithms, and Source Code Analysis
This article explains the need for distributed locks, compares Java native and JUC locks, details Redis‑based implementations such as RedissonLock and RedLock, introduces the WLock solution with WPaxos and RocksDB, and provides complete source‑code walkthroughs for lock acquisition and release.
In Java each object has an intrinsic lock that can be obtained with the synchronized keyword; since JDK 1.6 the JVM also provides biased and lightweight locks to reduce contention.
Java Concurrency Package (JUC) Locks
The JUC library offers advanced lock implementations (e.g., ReentrantLock , ReadWriteLock ) that support fine‑grained control and non‑blocking acquisition.
Why Distributed Locks?
When resources are shared across multiple processes or machines, a single‑process lock cannot guarantee consistency. A distributed lock ensures exclusive access to a shared resource across the whole system, preventing concurrent reads/writes that would corrupt data.
Requirements for a Distributed Lock
Exclusivity : only one client can hold the lock at any time.
Dead‑lock avoidance : the lock must be released after a bounded time, even if the client crashes.
High availability : acquisition and release must remain performant and tolerant to node failures.
Redis‑Based Distributed Lock (Redisson)
Redisson uses the Redis SETNX command to achieve exclusivity. By setting an expiration time the lock avoids dead‑locks. The library provides several lock types, such as RedissonLock (re‑entrant), RedissonFairLock , RedissonMultiLock , RedissonReadLock , RedissonWriteLock , RedissonTransactionalLock , and RedissonRedLock (the RedLock algorithm for multi‑node safety).
Typical usage:
//leaseTime表示设置的锁过期时间,unit表示时间单位,interruptibly表示获取锁期间是否响应中断
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
long threadId = Thread.currentThread().getId();
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl != null) {
RFuture
future = this.subscribe(threadId);
this.commandExecutor.syncSubscription(future);
try {
while (true) {
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
return; // lock acquired
}
if (ttl >= 0L) {
try {
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException var13) {
if (interruptibly) {
throw var13;
}
this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else if (interruptibly) {
this.getEntry(threadId).getLatch().acquire();
} else {
this.getEntry(threadId).getLatch().acquireUninterruptibly();
}
}
} finally {
this.unsubscribe(future, threadId);
}
}
}The core lock acquisition logic resides in tryAcquire , which eventually calls tryLockInnerAsync that executes a Lua script on Redis to atomically create the lock key, handle re‑entrancy, and return the remaining TTL when acquisition fails.
Unlocking follows a similar pattern: a Lua script decrements the re‑entrancy counter and deletes the key when the counter reaches zero, then publishes a release message so waiting clients can be awakened.
RedLock Algorithm
RedLock mitigates single‑node failures by acquiring the same lock on multiple independent Redis instances. A client must obtain the lock on a majority (N/2 + 1) of nodes within a short time window; the effective lock TTL is the original TTL minus the time spent acquiring the locks.
58‑Group WLock
WLock is a proprietary distributed lock service built on WPaxos (a Multi‑Paxos implementation) and RocksDB for persistent lock state. It supports mutex, re‑entrant, fair, and weighted locks, and offers both blocking and non‑blocking acquisition APIs.
Key configuration parameters include waitAcquire , expireTime , maxWaitTime , weight , renewInterval , and various listener callbacks for renewal and expiration events.
Sample acquisition code (blocking):
public AcquireLockResult tryAcquireLock(String lockkey, InternalLockOption lockOption) throws ParameterIllegalException {
AcquireLockResult result = new AcquireLockResult();
long startTimestamp = System.currentTimeMillis();
this.lockParameterCheck(lockOption);
if (this.lockManager.acquiredLockLocal(lockkey, lockOption)) {
LockContext lockContext = this.lockManager.getLocalLockContext(lockkey, lockOption.getThreadID());
this.renewLock(lockkey, lockContext.getLockVersion(), lockOption.getExpireTime(), lockOption.getThreadID());
result.setRet(true);
result.setOwner(new LockOwner(InetAddressUtil.getIpInt(), lockOption.getThreadID(), lockOption.getPID()));
result.setResponseStatus(ResponseStatus.SUCCESS);
result.setLockVersion(lockContext.getLockVersion());
return result;
} else {
int timeout = (int)Math.min(this.wlockClient.getDefaultTimeoutForReq(), lockOption.getMaxWaitTime());
WatchEvent watchEvent = null;
if (lockOption.isWaitAcquire()) {
watchEvent = new WatchEvent(lockkey, lockOption.getThreadID(), lockOption.getWatchID(), WatchType.ACQUIRE, startTimestamp);
watchEvent.setLockOption(lockOption);
watchEvent.setTimeout(lockOption.getMaxWaitTime());
this.watchManager.registerWatchEvent(lockkey, watchEvent);
}
int groupId = this.wlockClient.getRegistryKey().getGroupId();
AcquireLockRequest acquireLockReq = this.protocolFactory.createAcquireReq(lockkey, groupId, lockOption);
try {
SendReqResult sendReqResult = this.serverPoolHandler.syncSendRequest(acquireLockReq, timeout, "tryAcquireLock " + lockkey);
if (sendReqResult != null) {
AcquireLockResponse resp = new AcquireLockResponse();
resp.fromBytes(sendReqResult.getData());
if (resp.getStatus() == ResponseStatus.LOCK_WAIT) {
NotifyEvent notifyEvent = this.watchManager.waitNotifyEvent(lockOption.getWatchID(), lockOption.getMaxWaitTime());
if (notifyEvent != null && notifyEvent.getEventType() == EventType.LOCK_ACQUIRED.getType()) {
this.lockManager.updateLockLocal(lockkey, notifyEvent.getFencingToken(), lockOption, true);
EventCachedHandler.getInstance(this.wlockClient).unRegisterWatchEvent(lockkey, notifyEvent.getWatchID());
result.setRet(true);
result.setLockVersion(notifyEvent.getFencingToken());
result.setOwner(new LockOwner(acquireLockReq.getHost(), acquireLockReq.getThreadID(), acquireLockReq.getPid()));
result.setResponseStatus(ResponseStatus.SUCCESS);
} else {
result.setRet(false);
}
return result;
}
if (resp.getStatus() == ResponseStatus.SUCCESS) {
this.lockManager.updateLockLocal(lockkey, resp.getFencingToken(), lockOption, false);
result.setRet(true);
result.setLockVersion(resp.getFencingToken());
result.setOwner(new LockOwner(resp.getOwnerHost(), resp.getThreadID(), resp.getPid()));
return result;
}
result.setRet(false);
}
} catch (Exception e) {
logger.error(Version.INFO + ", tryAcquireLock error.", e);
} finally {
this.watchManager.unRegisterWatchEvent(lockkey, lockOption.getWatchID());
}
return result;
}
}Unlocking simply decrements the local re‑entrancy counter and, when it reaches zero, sends a remote unlock request via a Lua script that deletes the Redis key and publishes a release message.
Dead‑Lock Prevention
Both RedissonLock and WLock rely on client‑side lease renewal. If the thread that acquired the lock crashes before releasing it, the renewal thread may keep extending the lease, causing a dead‑lock. Therefore, lock acquisition should be wrapped in a try…finally block to guarantee unlock() execution.
Lock lock = new XxxLock();
lock.lock();
try {
// business logic
} finally {
lock.unlock();
}Overall, the article provides a comprehensive view of distributed lock mechanisms, their implementation details in Redis, Redisson, and the proprietary WLock, and practical guidance for safe usage in Java backend systems.
58 Tech
Official tech channel of 58, a platform for tech innovation, sharing, and communication.
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.