方式一:节点不可重名+watch
缺点:惊群效应
(流程图)
这个功能是需要实现juc的Lock接口。
1、参数,构造器
private String localpath;
private ZkClient client ;
public ZkDistributeLock(String localpath) {
if(localpath== null || localpath.trim().equals("")) {
throw new IllegalArgumentException("地址不能为空");
}
this.localpath = localpath;
//创建Zkclient客户端对象
client = new ZkClient("localhost:2181");
//设置序列化
client.setZkSerializer(new MyZkSerializer());
}
2、重写tryLock方法
//尝试加锁,就是去创建节点
public boolean tryLock() {
try {
client.createEphemeral(localpath);
} catch (Exception e) {
return false;
}
return true;
}
3、重写Lock方法
//所有线程争抢锁,如果没有获得锁,那么就要阻塞住
public void lock() {
if(!tryLock()) {
waitForLock();
//醒过来之后,继续进行获得锁操作
lock();
}
}
4、具体阻塞方法,使用了watch,CountDownWatch
//阻塞住线程,只有该节点被删除才会触发--,此时锁被释放,需要一个watch,监听
private void waitForLock() {
final CountDownLatch cdl = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
//该节点被删除,有人释放了锁
cdl.countDown();
}
public void handleDataChange(String dataPath, Object data) throws Exception {
// TODO Auto-generated method stub
}
};
//设置watch
client.subscribeDataChanges(localpath, listener);
try {
cdl.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//用完了(订阅)watch,就取消掉
client.unsubscribeDataChanges(localpath, listener);
}
5、重写UnLock
//删除节点,就是释放锁
public void unlock() {
client.delete(localpath);
}
方式二:取号+最小化+watch
1、参数和构造器
private String localPath;//父节点
private ZkClient client;
//当前序号
private ThreadLocal<String> currentPath = new ThreadLocal<String>();
//排在我前面的序号
private ThreadLocal<String> beforePath = new ThreadLocal<String>();
public ZkDistributeImproveLock(String localPath) {
if(localPath == null || localPath.trim().equals("")) {
throw new IllegalArgumentException("地址不能为空");
}
this.localPath = localPath;
client = new ZkClient("localhost:2181");
client.setZkSerializer(new MyZkSerializer());
//判断父节点是否存在,不存在则创建
if(!this.client.exists(localPath)) {
this.client.createPersistent(localPath, true);
}
}
2、重写tryLock()
public boolean tryLock() {
//创建临时顺序子节点
String node = client.createEphemeralSequential(localPath, "znode");
//这就是当前的节点
currentPath.set(node);
//获取父节点下所有的子节点
List<String> children = client.getChildren(localPath);
//排序
Collections.sort(children);
//如何当前自己的是最小的,则第一个获取锁
if(currentPath.get().equals(localPath+"/"+children.get(0))) {
return true;
}else {
//获取当前节点在list里面的下标
int curIndex = children.indexOf(currentPath.get().substring(localPath.length()+1));
String beforeNode = localPath+"/"+children.get(curIndex-1);
beforePath.set(beforeNode);
}
return false;
}
3、重写Lock()
public void lock() {
if(!tryLock()) {
waitForLock();
//唤醒,重新获取锁
lock();
}
}
private void waitForLock() {
final CountDownLatch cdl = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
//该节点被删除,有人释放了锁
cdl.countDown();
}
public void handleDataChange(String dataPath, Object data) throws Exception {
// TODO Auto-generated method stub
}
};
//设置watch
client.subscribeDataChanges(beforePath.get(), listener);
//如何前一个节点被删除了,则不去等待了
if(this.client.exists(this.beforePath.get())) {
try {
cdl.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//用完了(订阅)watch,就取消掉
client.unsubscribeDataChanges(beforePath.get(), listener);
}
4、重写UnLock()
public void unlock() {
client.delete(currentPath.get());
}