闲来无事,用Java的zk客户端Curator实现了一个简易的分布式可重入锁,这里的可重入指的是线程可重入,如果有需求是应用可重入只需要稍加修改,将获取锁的状态记录即可,例如最简单的记到static字段,那么一个部署实例就可以共享一个锁状态。另外,如果没有而外的需求,还是直接使用Curator-recipes项目的InterProcessMutex作为分布式锁比较好,毕竟Apache项目现成的轮子。
实现的思路在网上有很多,Zookeeper实现分布式锁 里的流程图画得很好,或者可以直接看InterProcessMutex的实现思路,总体来说有点类似AQS,把等待锁的候选人排成一个队列,从前往后赋予资源。
构造参数
public DistributedLock(CuratorFramework client, String basePath) {
this.client = client;
this.basePath = basePath;
}
client从外部注入由外部决定连接的细节,锁内部只需要client提供的api。basePath是锁路径,用同一个锁路径初始化分布式锁,当使用的是同一个zk客户端时,不同的DistributedLock实例也能有互斥的效果(同一个app不同线程、不同app)。
lock
public boolean tryLock(long time, TimeUnit unit) {
//当前线程正在持有锁
ThreadContext threadContext = threadLocal.get();
if (threadContext != null) {
threadContext.lockCount++;
return true;
}
//等待时间的处理
long startTime = System.currentTimeMillis();
long waitTime = unit.toMillis(time);
boolean getLock = false;
//生成候选人结点
try {
String path = ZKPaths.makePath(basePath, LOCK_NAME);
selfPath = client
.create()
.creatingParentsIfNeeded()
.withProtection()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(path);
log.info("lock self path {}", selfPath);
} catch (Exception exception) {
deleteSelf();
throw new RuntimeException(exception);
}
while (true) {
try {
//获取所有的候选人结点
List children = client.getChildren().forPath(basePath);
List sortedList = Lists.newArrayList(children);
Collections.sort(sortedList);
String selfNode = ZKPaths.getNodeFromPath(selfPath);
//获取当前候选人所在的位置
int selfIndex = sortedList.indexOf(selfNode);
if (selfIndex < 0) {
throw new IllegalMonitorStateException();
}
//如果是第一个结点,则获得锁
if (selfIndex == 0) {
getLock = true;
break;
}
//监控前一个结点,当前一个结点释放锁时,当前候选人获取锁
synchronized (this) {
String previousSequencePath = ZKPaths.makePath(basePath, sortedList.get(selfIndex - 1));
log.info("watch {}", previousSequencePath);
client.getData().usingWatcher(watcher).forPath(previousSequencePath);
if (waitTime == 0) {
break;
} else if (waitTime > 0) {
long diff = System.currentTimeMillis() - startTime;
if (diff < waitTime) {
wait(waitTime - diff);
} else {
deleteSelf();
break;
}
} else {
wait();
log.info("wake up");
}
}
} catch (Exception e) {
deleteSelf();
throw new RuntimeException(e);
}
}
//记录线程获取锁的上下文
if (getLock) {
threadLocal.set(new ThreadContext(1));
}
return getLock;
}
unlock
public void unlock() {
ThreadContext context = threadLocal.get();
if (context == null) {
throw new IllegalMonitorStateException("Thread not have lock");
}
if (context.lockCount == 1) {
deleteSelf();
threadLocal.set(null);
} else {
context.lockCount--;
}
}
详细代码见:分布式锁