// 循环等待来激活分布式锁,实现锁的公平性
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception {
// 是否已经持有分布式锁
boolean haveTheLock = false;
// 是否需要删除子节点
boolean doDelete = false;
try {
if (revocable.get() != null) {
client.getData().usingWatcher(revocableWatcher).forPa
th(ourPath);
}
while ((client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock) {
// 获取排序后的子节点列表
List<String> children = getSortedChildren();
// 获取前面自己创建的临时顺序子节点的名称
String sequenceNodeName = ourPath.substring(basePath.length() + 1);
// 实现锁的公平性的核心逻辑,看下面的分析
PredicateResults predicateResults = driver.getsTheLock(client, children , sequenceNodeName , maxLeases);
if (predicateResults.getsTheLock()) {
// 获得了锁,中断循环,继续返回上层
haveTheLock = true;
} else {
// 没有获得到锁,监听上一临时顺序节点
String previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();
synchronized (this) {
try {
// exists()会导致导致资源泄漏,因此exists()可以监听不存在的ZNode,因此采用getData()
// 上一临时顺序节点如果被删除,会唤醒当前线程继续竞争锁,正常情况下能直接获得锁,因为锁是公平的
client.getData().usingWatcher(watcher).forPath(previousSequencePath);
if (millisToWait != null) {
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if (millisToWait <= 0) {
doDelete = true; // 获取锁超时,标记删除之前创建的临时顺序节点
break;
}
wait(millisToWait);// 等待被唤醒,限时等待
} else {
wait(); // 等待被唤醒,无限等待
}
} catch (KeeperException.NoNodeException e) {
// 容错处理,逻辑稍微有点绕,可跳过,不影响主逻辑的理解
// client.getData()可能调用时抛出NoNodeException,原因可能是锁被释放或会话过期(连接丢失)等
// 这里并没有做任何处理,因为外层是while循环,再次执行driver.getsTheLock时会调用validateOurIndex
// 此时会抛出NoNodeException,从而进入下面的catch和finally逻辑,重新抛出上层尝试重试获取锁并删除临时顺序节点
}
}
}
}
} catch (Exception e) {
ThreadUtils.checkInterrupted(e);
// 标记删除,在finally删除之前创建的临时顺序节点(后台不断尝试)
doDelete = true;
// 重新抛出,尝试重新获取锁
throw e;
} finally {
if (doDelete) {
deleteOurPath(ourPath);
}
}
return haveTheLock;
}
版权声明:本文为Leon_Jinhai_Sun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。