ZooKeeper实现分布式锁
![image-20210804080321296](https://gitee.com/studylihai/blog-images/raw/master/img/20210804080321.png)
public class DistributedLock {
// zookeeper server 列表
private String connectString = "xx.xx.xx.xx:2181";
// 超时时间
private int sessionTimeout = 20000;
private ZooKeeper zk;
private String rootNode = "locks";
private String subNode = "seq-";
// 当前 client 等待的子节点
private String waitPath;
//ZooKeeper 连接
private CountDownLatch connectLatch = new CountDownLatch(1);
//ZooKeeper 节点等待
private CountDownLatch waitLatch = new CountDownLatch(1);
// 当前 client 创建的子节点
private String currentNode;
// 在构造方法里 和 zk 服务建立连接,并创建根节点
public DistributedLock() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
//对应的监听代码!
@Override
public void process(WatchedEvent event) {
// 连接建立时, 打开 latch, 唤醒 wait 在该 latch 上的线程
if (event.getState() == Event.KeeperState.SyncConnected) {
connectLatch.countDown();
}
// 发生了 waitPath 的删除事件 (如果前一个节点被删除!)
if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {
waitLatch.countDown();
}
}
});
// 等待连接建立后,往下执行程序!
connectLatch.await();
//获取根节点状态,不用监听。
Stat stat = zk.exists("/" + rootNode, false);
//如果根节点不存在,则创建根节点,根节点类型为永久节点。
if (stat == null) {
System.out.println("根节点不存在");
zk.create("/" + rootNode, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 加锁方法
public void zkLock() {
try {
//在根节点下创建带序号临时顺序节点,返回值为创建的节点路径。
currentNode = zk.create("/" + rootNode + "/" + subNode, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// wait 一小会, 让结果更清晰一些。
Thread.sleep(10);
// 注意, 没有必要监听"/locks"的子节点的变化情况。
List<String> childrenNodes = zk.getChildren("/" + rootNode,false);
// 列表中只有一个子节点, 那肯定就是 currentNode , 说明client 获得锁。
if (childrenNodes.size() == 1) {
return;
} else {
//有多个节点,对根节点下的所有临时顺序节点进行从小到大排序。
Collections.sort(childrenNodes);
//当前节点名称 比如seq-0000000。
String thisNode = currentNode.substring(("/" + rootNode + "/").length());
//获取当前节点在所有子节点集合中的索引位置。
int index = childrenNodes.indexOf(thisNode);
if (index == -1) {
System.out.println("数据异常");
} else if (index == 0) {
// index == 0, 说明 thisNode 在列表中最小,当前client获得锁。
return;
} else {
// 获得排名比 currentNode 前 1 位的节点。
this.waitPath = "/" + rootNode + "/" + childrenNodes.get(index - 1);
// 在 waitPath 上注册监听器, 当 waitPath 被删除时,zookeeper 会回调监听器的 process 方法。
zk.getData(waitPath, true, null);
//进入等待锁状态 监听没结束一直等待。
waitLatch.await();
return;
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 解锁方法
public void zkUnlock() {
try {
zk.delete(this.currentNode,-1);
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
}
测试
public class DistributedLockTest {
public static void main(String[] args) throws Exception {
// 创建分布式锁 1
final DistributedLock lock1 = new DistributedLock();
// 创建分布式锁 2
final DistributedLock lock2 = new DistributedLock();
new Thread(new Runnable() {
@Override
public void run() {
// 获取锁对象
try {
lock1.zkLock();
System.out.println("线程 1 获取锁");
Thread.sleep(2 * 1000);
lock1.zkUnlock();
System.out.println("线程 1 释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 获取锁对象
try {
lock2.zkLock();
System.out.println("线程 2 获取锁");
Thread.sleep(2 * 1000);
lock2.zkUnlock();
System.out.println("线程 2 释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}