使用Zookeeper分布式锁时,一般会使用Curator中的实现。本文主要分析一下Curator中的可重入锁 InterProcessMutex 的源码实现
- 从锁的获取acquire()方法开始
public void acquire() throws Exception {
if (!internalLock(-1, null) ){
throw new IOException("Lost connection while trying to acquire lock: " + basePath);
}
}
private boolean internalLock(long time, TimeUnit unit) throws Exception
{
Thread currentThread = Thread.currentThread();
// threadData是一个ConcurrentHashMap,用于存放每个线程对应的锁数据
LockData lockData = threadData.get(currentThread);
// 如果存在锁数据,说明当前线程获取过锁;因为是可重入锁,返回true获取锁即可
if ( lockData != null )
{
lockData.lockCount.incrementAndGet();
return true;
}
// 否则需要去为该线程获取锁
String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
// 把信息存入lockData中
if ( lockPath != null )
{
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}
return false;
}
- 接下来来看attemptLock()具体是怎么获取锁的
String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{
....
String ourPath = null;
boolean hasTheLock = false;
boolean isDone = false;
while ( !isDone )
{
isDone = true;
try
{
// 1.在Zookeeper下创建锁节点
ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
// 2.尝试获取锁
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
}
catch ( KeeperException.NoNodeException e )
{
....
}
if ( hasTheLock ){
return ourPath;
}
return null;
}
- 第一步:在Zookeeper下创建锁节点,从下面源码可以看到可重入锁实际就是在Zookeeper下创建临时有序节点
@Override
public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception {
String ourPath;
if ( lockNodeBytes != null ){
ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);
} else {
ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path);
}
return ourPath;
}
- 第二步:尝试获取锁
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception {
boolean haveTheLock = false;
boolean doDelete = false;
try {
.....
// 如果客户端状态正常且还没有获取到锁
while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock ){
// 将锁路径下的所有临时有序节点进行排序
List<String> children = getSortedChildren();
String sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
// 尝试获取锁
PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
// 如果获取到了,haveTheLock设置为true,不会进入下一次循环
if ( predicateResults.getsTheLock() ) {
haveTheLock = true;
}
else {
// 否则获取次小节点
String previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();
synchronized(this){
try {
// 给次小节点添加Watch监听
client.getData().usingWatcher(watcher).forPath(previousSequencePath);
if ( millisToWait != null ) {
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if ( millisToWait <= 0 )
{
doDelete = true; // timed out - delete our node
break;
}
// 如果规定了超时时间,则在规定时间内等待
wait(millisToWait);
}
else{
// 否则一直阻塞在这等待被唤醒
// 锁被释放后,从这里被唤醒,进入下一次循环,直至当前节点为最小节点,成功获取锁
wait();
}
}
catch ( KeeperException.NoNodeException e )
{
// it has been deleted (i.e. lock released). Try to acquire again
}
}
}
}
}
catch ( Exception e )
{
...
}
return haveTheLock;
}
- 来看getsTheLock()是怎么判断能否获取锁的
@Override
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception{
// 获取当前节点的排序
int ourIndex = children.indexOf(sequenceNodeName);
validateOurIndex(sequenceNodeName, ourIndex);
// 对于可重入锁,maxLeases为1
// 说明只有是序号最小的节点才能成功获取锁
boolean getsTheLock = ourIndex < maxLeases;
// 并找出次小的节点添加监听
String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);
return new PredicateResults(pathToWatch, getsTheLock);
}
这么一来,锁的获取就可以一个一个传递下去,并且一次只唤醒一个,不会导致羊群效应