逻辑:
- 接收到请求后,在/locks节点下创建一个临时顺序节点
- 判断自己是不是当前节点下最小的节点,是,获取锁,不是,对前一个节点进行监听
- 获取到锁,处理完业务后,delete节点释放锁,然后下面的节点将收到通知,重复第二步判断。
编码实现:
新建类DistributedLock,通过构造方法进行逻辑处理
public DistributedLock() throws IOException,KeeperException,InterruptedException
建立zookeeper连接
//1.建立连接
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
//等待zk正常连接后,往下走程序
countDownLatch.await();
判断根节点/locks是否存在:
//2.判断根节点/locks是否存在
Stat stat = zk.exists("/locks",false);
if (stat == null){
//创建一下根节点
zk.create("/locks","locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
加锁:
//创建对应的临时带序号的节点
try {
String currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
//判断当前节点是不是最小序号节点,如果是获取到锁,如果不是,监听他序号前一个节点
List<String> children = zk.getChildren("/locks", false);
//如果children只有一个值,那就直接获得锁,如果有多个节点,需要判断,
if (children.size()==1){
}else {
Collections.sort(children);
//获取节点名称
String thisNode = currentMode.substring("/locks/".length());
//通过seq-00000000获取该节点在children集合的位置
int index = children.indexOf(thisNode);
//判断
if (index == -1){
System.out.println("数据异常");
}else if (index == 0){
//就一个节点,可以获取锁
return;
}else {
//需要监听 他前一个节点变化
waitPath = "/locks/"+children.get(index - 1);
zk.getData(waitPath,true,null);
//等待监听
waitLatch.await();
return;
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
解锁: 就是删除节点
//删除节点
try {
zk.delete(currentMode,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
整体代码就写完了,如下:
public class DistributedLock {
private String connectString = "192.168.59.128:2181,192.168.59.129:2181,192.168.59.130:2181";
private int sessionTimeout = 2000;
private ZooKeeper zk;
private CountDownLatch countDownLatch = new CountDownLatch(1);
private CountDownLatch waitLatch = new CountDownLatch(1);
private String waitPath;
private String currentMode;
public DistributedLock() throws IOException,KeeperException,InterruptedException{
//1.建立连接
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
//countDownLatch表示如果连接上zk,可以释放
if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();
}
//waitLatch也需要释放
if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
waitLatch.countDown();
}
}
});
//等待zk正常连接后,往下走程序
countDownLatch.await();
//2.判断根节点/locks是否存在
Stat stat = zk.exists("/locks",false);
if (stat == null){
//创建一下根节点
zk.create("/locks","locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
}
//3.对zk加锁
public void zkLock(){
//创建对应的临时带序号的节点
try {
currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
//判断当前节点是不是最小序号节点,如果是获取到锁,如果不是,监听他序号前一个节点
List<String> children = zk.getChildren("/locks", false);
//如果children只有一个值,那就直接获得锁,如果有多个节点,需要判断,
if (children.size()==1){
}else {
Collections.sort(children);
//获取节点名称
String thisNode = currentMode.substring("/locks/".length());
//通过seq-00000000获取该节点在children集合的位置
int index = children.indexOf(thisNode);
//判断
if (index == -1){
System.out.println("数据异常");
}else if (index == 0){
//就一个节点,可以获取锁
return;
}else {
//需要监听 他前一个节点变化
waitPath = "/locks/"+children.get(index - 1);
zk.getData(waitPath,true,null);
//等待监听
waitLatch.await();
return;
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//4.解锁
public void unZkLock() {
//删除节点
try {
zk.delete(currentMode,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
编写DistributedLockTest类进行功能测试
/**
* 测试模拟两个线程获取锁
*/
public class DistributedLockTest {
public static void main(String[] args) throws InterruptedException, KeeperException, IOException {
final DistributedLock distributedLock1 = new DistributedLock();
final DistributedLock distributedLock2 = new DistributedLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
distributedLock1.zkLock();
System.out.println("线程1启动,获取到锁");
Thread.sleep(5*1000);
distributedLock1.unZkLock();
System.out.println("线程1释放锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
distributedLock2.zkLock();
System.out.println("线程2启动,获取到锁");
Thread.sleep(5*1000);
distributedLock2.unZkLock();
System.out.println("线程2释放锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
启动:日志打印