Java Distributed Lock

1. The concept of distributed locks

A distributed lock is a mechanism for coordinating mutual exclusion access to shared resources by multiple processes/services in a distributed system. In a single-machine system, we can use Java’s built-in locking mechanisms (such as synchronized` synchronized` and ` ReentrantLocksynchronized`) to achieve thread synchronization. However, in a distributed environment, these native locking mechanisms cannot work across JVMs, thus requiring distributed locks.

Typical application scenarios include:

  • Prevent duplicate order submissions
  • Inventory deduction in the flash sale system
  • Distributed scheduling of scheduled tasks
  • Cache updates in a distributed environment

2. Implementation methods of distributed locks

2.1 Database-based implementation

Implementation principle : Distributed locks are implemented using the uniqueness constraints or row-level locking features of the database. Common methods include:

  1. Create a lock table and use a unique index to prevent duplicate lock acquisitions.
  2. Use SELECT ... FOR UPDATEstatements to lock records.

Example code :

// Distributed Lock Implementation Based on MySQL
public class DatabaseDistributedLock {
private DataSource dataSource;

public boolean tryLock(String lockName, long timeout) {
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
// Using FOR Update with Row Lock
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE");
stmt.setString(1, lockName);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
// Insert if lock does not exist
PreparedStatement insertStmt = conn.prepareStatement(
"INSERT INTO distributed_lock(lock_name, owner, create_time) VALUES (?, ?, ?)");
insertStmt.setString(1, lockName);
insertStmt.setString(2, Thread.currentThread().getName());
insertStmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
insertStmt.executeUpdate();
}
conn.commit();
return true;
} catch (SQLException e) {
return false;
}
}
}

Advantages and disadvantages :

  • Advantages: Simple to implement, no additional middleware required.
  • Disadvantages: Poor performance (high database I/O overhead), complex implementation of non-blocking locks, and risk of single point of failure.

2.2 Redis-based implementation

Implementation principle : Mutual exclusion is achieved using Redis’s SETNX (SET if Not eXists) command, and deadlock is prevented by setting an expiration time.

RedLock algorithm (Redis’s officially recommended distributed lock implementation):

  1. Get the current time (milliseconds)
  2. Attempt to acquire the lock from N independent Redis nodes in turn.
  3. A lock acquisition is considered successful only if the total time spent acquiring the lock (is less than the lock timeout) and a majority of nodes (N/2+1) are acquired.
  4. The actual effective time of the lock = Initial effective time – Time spent acquiring the lock
  5. If acquiring the lock fails, a lock release command is sent to all nodes.

Example code (using the Redisson client) :

// Implement a distributed lock using Redisson 
public class RedisDistributedLockExample {
public void doWithLock() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");

RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");

try {
// Attempt to acquire the lock, waiting up to 100 seconds, automatically releasing it after 10 seconds
boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (isLocked) {
// Execute business logic
System.out.println("Lock acquired, doing business logic...");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
redisson.shutdown();
}
}
}

Key issues with Redis distributed locks :

  1. Atomicity : Use Lua scripts to ensure the atomicity of the SETNX and EXPIRE operations.
  2. Lock renewal : The lock’s validity period is periodically checked and extended by a daemon thread.
  3. Lock release : Ensures that only the lock holder can release the lock (a unique identifier is stored in the value).
  4. Cluster fault tolerance : Master-slave switching may cause lock loss; the RedLock algorithm provides a certain solution.

2.3 Implementation based on Zookeeper

Implementation principle : Distributed locks are implemented using Zookeeper’s ephemeral sequential nodes and Watch mechanism.

Implementation steps :

  1. Create a temporary sequential node at the specified path.
  2. Get all child nodes under the parent node and sort them.
  3. Determine if the current node is the node with the smallest sequence number:
    • Yes, the lock acquisition was successful.
    • Otherwise, register a Watcher for the previous node.
  4. After the lock is released, ZooKeeper will notify the next waiting node.

Example code (using the Curator framework) :

public class ZookeeperDistributedLock {
private CuratorFramework client;
private InterProcessMutex lock;

public ZookeeperDistributedLock(String connectString, String lockPath) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
lock = new InterProcessMutex(client, lockPath);
}

public void doWithLock(Runnable task) throws Exception {
try {
// Get lock, wait for up to 5 seconds
if (lock.acquire(5, TimeUnit.SECONDS)) {
task.run();
}
} finally {
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
}
}
}

Features of Zookeeper locks :

  • High reliability: Based on the CP model, data consistency is guaranteed.
  • Automatic release: Ephemeral nodes are automatically deleted when the session ends or is interrupted.
  • Fair lock: Locks are acquired according to the order in which nodes are created.
  • Good performance: compared to database solutions, but slightly lower than Redis.

3. Key characteristics of distributed locks

A complete distributed lock implementation should have the following characteristics:

  1. Mutual exclusion : Only one client can hold the lock at any given time.
  2. To avoid deadlock : Locks must have a timeout mechanism or an automatic release mechanism.
  3. Fault tolerance : The lock service remains available even if some nodes fail.
  4. Reentrancy : The same client can acquire the same lock multiple times.
  5. High performance : The operations of acquiring and releasing locks must be efficient.
  6. Fairness : The order in which locks are acquired must match the order in which they are requested (optional)

4. Best Practices for Distributed Locks

  1. Selection of lock granularity :
    • Fine-grained locking: precise resource contention, but complex management.
    • Coarse-grained locks: Simple to implement, but with low concurrency.
  2. Timeout settings :
    • Lock acquisition timeout: to avoid long waiting times.
    • Lock holding timeout: The business operation should be completed within this time.
  3. Releasing the lock :
    • It must be placed in a finally block to ensure that the resource is released.
    • To achieve reentrancy of a lock, the counter must be maintained correctly.
  4. Exception handling :
    • Network partitioning handling strategy
    • Degradation plan when lock service is unavailable
  5. Monitoring and Alarms :
    • Success rate of acquiring surveillance locks
    • Average holding time of surveillance lock

5. Common Problems and Solutions

Question 1: Lock expires early

  • Phenomenon: The lock expired before the business operation was completed.
  • Solution: Implement a lock renewal mechanism (watchdog)

Question 2: Incorrectly releasing another person’s lock

  • Phenomenon: Client A released the lock of Client B.
  • Solution: Store a unique identifier in the lock’s value and verify it upon release.

Question 3: Network partitioning leads to split-brain

  • Phenomenon: Multiple clients simultaneously hold the lock
  • Solution: Use a fencing token mechanism

Question 4: Lock is not reentrant

  • Phenomenon: Deadlock caused by the same thread acquiring the lock multiple times.
  • Solution: Maintain the holder thread and the counter.

6. Summary and Selection Recommendations

  1. Database locks : Suitable for scenarios with low concurrency, low reliability requirements, and existing database environments.
  2. Redis locks : Suitable for high-performance, high-availability scenarios, but consistency requirements cannot be too high.
  3. Zookeeper locks : suitable for scenarios requiring strong consistency and high reliability, but with relatively low performance.

In real-world projects, it is recommended to use mature frameworks such as Redisson or Curator, as they have already handled various boundary conditions and exceptional situations. For critical business logic, combining multiple implementation methods can improve reliability.