public static void main(String[] args) throws Exception {
//创建zookeeper的客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
client.start();
//创建分布式锁, 锁空间的根节点路径为/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
mutex.acquire();
//获得了锁, 进行业务流程
System.out.println("Enter mutex");
//完成业务流程, 释放锁
mutex.release();
//关闭客户端
client.close();
}
我们使用了curator这个开源项目提供的zookeeper分布式锁实现,上面就是利用锁的代码,我们从获取锁开始进行分析:
public void acquire() throws Exception
{
if ( !internalLock(-1, null) )
{
throw new IOException("Lost connection while trying to acquire lock: " + basePath);
}
}
我们没有设置最长等待时间,所以利用internalLock方法一直阻塞到获取到锁或者失去连接为止:
private boolean internalLock(long time, TimeUnit unit) throws Exception
{
//获取到当前线程
Thread currentThread = Thread.currentThread();
//获取当前锁的信息
LockData lockData = threadData.get(currentThread);
//如果信息不为null,说明已经获取到锁了,增加一次锁的计数即可
if ( lockData != null )
{
// re-entering
lockData.lockCount.incrementAndGet();
return true;
}
//获取锁
String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
//说明获得到了锁
if ( lockPath != null )
{
//将当前锁的信息记录下来
LockData newLockData = new LockData(currentThread, lockPath);
threadData.put(currentThread, newLockData);
return true;
}
return false;
}
首先判断当前线程有没有获取到锁,如果已经获取到锁了,支持可重入,对获得到的锁的次数+1,否则尝试获取锁,获取成功后,将当前线程获取到的锁的信息记录下来
String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{
...
while ( !isDone )
{
isDone = true;
try
{
//在指定的锁的目录下面创建一个有序的临时节点
ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
//一直阻塞到获取到锁
hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
}
catch ( KeeperException.NoNodeException e )
{
// gets thrown by StandardLockInternalsDriver when it can't find the lock node
// this can happen when the session expires, etc. So, if the retry allows, just try it all again
if ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) )
{
isDone = false;
}
else
{
throw e;
}
}
}
if ( hasTheLock )
{
return ourPath;
}
return null;
}
在指定的锁的目录下面创建一个有序的临时节点,然后返回这个节点的路径信息,判断是否能获取到锁,如果获取到了,直接返回,否则阻塞到或者等待过期后返回
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;
}
根据lockNodeBytes是否为null进入不同的分支,在这我们进入的是第二个分支
public CreateBuilder create()
{
Preconditions.checkState(getState() == CuratorFrameworkState.STARTED, "instance must be started before calling this method");
return new CreateBuilderImpl(this);
}
首先判断状态是否是启动状态,如果启动了才继续创建一个临时且有序的节点,我们接下来看创建好节点之后是如何获得锁的:
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).forPath(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
{
//监控它的上一个节点
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 )
{
ThreadUtils.checkInterrupted(e);
doDelete = true;
throw e;
}
finally
{
if ( doDelete )
{
deleteOurPath(ourPath);
}
}
return haveTheLock;
}
首先它会获得当前锁空间下的所有子节点,然后根据自己的序号尝试获取锁,如果获取失败,那么就先获取它的前驱结点,设置好监听器,然后将自己挂起
public void release() throws Exception
{
//获取当前线程的获取的锁的信息
Thread currentThread = Thread.currentThread();
LockData lockData = threadData.get(currentThread);
if ( lockData == null )
{
throw new IllegalMonitorStateException("You do not own the lock: " + basePath);
}
int newLockCount = lockData.lockCount.decrementAndGet();
//判断是否是可重入锁
if ( newLockCount > 0 )
{
return;
}
if ( newLockCount < 0 )
{
throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + basePath);
}
try
{
//如果所有的所资源都释放了,那么就需要释放这个节点
internals.releaseLock(lockData.lockPath);
}
finally
{
threadData.remove(currentThread);
}
}
在释放锁的时候首先会先判断,当前线程是否释放了所有的锁资源,如果没有,减少资源的次数后直接返回,否则需要是否当前节点:
private void deleteOurPath(String ourPath) throws Exception
{
try
{
client.delete().guaranteed().forPath(ourPath);
}
catch ( KeeperException.NoNodeException e )
{
// ignore - already deleted (possibly expired session, etc.)
}
}
最终删除这个节点