以下是基于 Java 语言(使用 Apache Curator 框架,ZooKeeper 官方推荐的客户端,已封装分布式锁核心逻辑)的分布式锁 Demo,包含 加锁、执行业务、释放锁 完整流程,代码简洁且符合生产级规范。
一、前置依赖
需在 Maven 项目的 pom.xml 中引入 Curator 依赖(Curator 已封装 ZooKeeper 原生 API 的复杂逻辑,如连接管理、Watcher 监听、重试机制):
<dependencies>
<!--// Zookeeper Curator client-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.5.0</version>
</dependency>
<!--Zookeeper client-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.1</version>
</dependency>
</dependencies>
二、ZooKeeper 分布式锁
核心逻辑:通过 Curator 的 InterProcessMutex(基于临时顺序节点实现的可重入分布式锁),模拟多个线程(对应分布式场景下的多个服务节点)竞争锁,确保同一时间仅一个线程执行“临界区业务”。
public class zookeeperTest {
//测试zookeeper的分布式锁
private static final String ZK_ADDRESS = "localhost:2181";
//分布式锁路径
private static final String LOCK_PATH = "/my_lock";
//模拟的线程数量
private static final int THREAD_COUNT = 5;
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
//循环创建线程,模拟并发抢锁
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
new Thread(new Runnable() {
public void run() {
CuratorFramework zkClient = null;
InterProcessMutex zkLock = null;
try {
zkClient = CuratorFrameworkFactory.builder().connectString(ZK_ADDRESS)
//设置会话超时时间和连接超时时间
.sessionTimeoutMs(5000)
.connectionTimeoutMs(3000)
//设置重试策略
.retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
//启动客户端
zkClient.start();
//创建分布式锁
zkLock = new InterProcessMutex(zkClient, LOCK_PATH);
//尝试获取锁,最多等待10秒
boolean isLockAcquire = zkLock.acquire(10, TimeUnit.SECONDS);
if (isLockAcquire) {
System.out.println("Thread " + threadId + " 成功获取分布式锁,执行临界区业务");
//模拟业务处理
Thread.sleep(2000);
System.out.println("Thread " + threadId + " 业务处理完成,释放锁");
} else {
//获取锁失败,处理相应逻辑
System.out.println("Thread " + threadId + " 没有获取到锁.");
}
} catch (Exception e) {
System.out.println("Thread " + threadId + " 处理异常: " + e.getMessage());
e.printStackTrace();
} finally {
//释放锁和关闭客户端
if (zkLock != null && zkLock.isAcquiredInThisProcess()) {
try {
zkLock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
if (zkClient != null) {
zkClient.close();
}
//确保latch递减,防止主线程一直等待
latch.countDown();
}
}
}).start();
}
try {
latch.await();
System.out.println("所有线程处理完成,主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、代码核心说明
1.Curator 客户端初始化:
-
ExponentialBackoffRetry:失败重试策略,避免因网络波动导致连接失败,生产环境建议配置合理的重试次数和等待时间。
-
客户端需调用 start() 启动,close() 关闭,避免资源泄漏。
2.分布式锁核心类 InterProcessMutex:
-
底层基于 ZooKeeper 临时顺序节点 和 Watcher 监听 实现,无需手动处理节点创建、排序、监听逻辑。
-
支持 可重入(同一线程多次 acquire() 不会死锁)和 超时等待(acquire(timeout, unit) 避免无限阻塞)。
3.锁释放逻辑:
-
必须在 finally 中调用 release(),确保无论业务成功/失败,锁都能释放。
-
释放前需通过 isAcquiredInThisProcess() 判断当前线程是否持有锁,避免误释放其他线程的锁。
四、运行前提与结果
1.运行前提:
-
已启动 ZooKeeper 集群(单机也可,地址改为 127.0.0.1:2181),且集群状态正常。
-
确保程序有权限操作 ZooKeeper 节点(默认无权限控制,生产环境需配置 ACL)。
2.预期运行结果:
5个线程会按“先请求先获取”的公平顺序抢锁,同一时间仅 1 个线程执行业务,执行完释放锁后,下一个线程才能获取锁,示例输出如下:
Thread 3 成功获取分布式锁,执行临界区业务
Thread 3 业务处理完成,释放锁
Thread 2 成功获取分布式锁,执行临界区业务
Thread 2 业务处理完成,释放锁
Thread 4 成功获取分布式锁,执行临界区业务
Thread 4 业务处理完成,释放锁
Thread 0 成功获取分布式锁,执行临界区业务
Thread 0 业务处理完成,释放锁
Thread 1 成功获取分布式锁,执行临界区业务
Thread 1 业务处理完成,释放锁
所有线程处理完成,主线程继续执行
五、生产环境注意事项
-
锁路径设计:建议按“业务类型+资源标识”设计锁路径(如 /distribute-lock/order/1001,1001 为订单ID),避免全局锁导致性能瓶颈。
-
会话超时配置:sessionTimeoutMs 需大于业务最大执行时间,避免客户端会话超时导致锁被自动释放(临时节点随会话消失)。
-
集群高可用:ZooKeeper 集群需至少 3 个节点,确保宕机 1 个节点后仍能正常提供服务,避免锁服务不可用。