一、主要需要解决的问题
1、加锁
创建临时带有顺序的节点,如果是第一个则,获取锁成功
2、解锁
删除自己创建的临时节点
3、死锁问题
临时节点,如果客户端down机了,则节点监听自动删除
4、锁等待与唤醒
每个等待的线程等待上一个节点删除,一旦删除,则自己被唤醒去获取锁
使用模板设计模式进行处理
二、锁接口
public interface Lock { //获取到锁的资源 public void getLock(); // 释放锁 public void unLock(); }
三、抽象锁
public abstract class AbstractLock implements Lock{ public void getLock() { //任务通过竞争获取锁才能对该资源进行操作(①竞争锁); // 当有一个任务在对资源进行更新时(②占有锁), // 其他任务都不可以对这个资源进行操作(③任务阻塞), // 直到该任务完成更新(④释放锁) //尝试获得锁资源 //①竞争锁 if (tryLock()) { System.out.println("##获取lock锁的资源####"); } else { //③任务阻塞 waitLock(); // 重新获取锁资源 getLock(); } } // ②占有锁 public abstract boolean tryLock(); // 等待 public abstract void waitLock(); }
四、抽象锁的公共参数
public abstract class ZookeeperAbstractLock extends AbstractLock { // zk连接地址 private static final String CONNECTSTRING = "127.0.0.1:2181"; // 创建zk连接 protected ZkClient zkClient = new ZkClient(CONNECTSTRING); protected static final String PATH = "/lock"; protected static final String PATH2 = "/lock2"; }
五、具体实现
public class ZookeeperDistrbuteLock2 extends ZookeeperAbstractLock { private CountDownLatch countDownLatch= null; private String beforePath;//当前请求的节点前一个节点 private String currentPath;//当前请求的节点 public ZookeeperDistrbuteLock2() { if (!this.zkClient.exists(PATH2)) { this.zkClient.createPersistent(PATH2); } } @Override public boolean tryLock() { //如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath if(currentPath == null || currentPath.length()<= 0){ //创建一个临时顺序节点 currentPath = this.zkClient.createEphemeralSequential(PATH2 + '/',"lock"); } //获取所有临时节点并排序,临时节点名称为自增长的字符串如:0000000400 List<String> childrens = this.zkClient.getChildren(PATH2); Collections.sort(childrens); if (currentPath.equals(PATH2 + '/'+childrens.get(0))) {//如果当前节点在所有节点中排名第一则获取锁成功 return true; } else {//如果当前节点在所有节点中排名中不是排名第一,则获取前面的节点名称,并赋值给beforePath int wz = Collections.binarySearch(childrens, currentPath.substring(7)); beforePath = PATH2 + '/'+childrens.get(wz-1); } return false; } @Override public void waitLock() { IZkDataListener listener = new IZkDataListener() { public void handleDataDeleted(String dataPath) throws Exception { if(countDownLatch!=null){ countDownLatch.countDown(); } } public void handleDataChange(String dataPath, Object data) throws Exception { } }; //给排在前面的的节点增加数据删除的watcher,本质是启动另外一个线程去监听前置节点 this.zkClient.subscribeDataChanges(beforePath, listener); if(this.zkClient.exists(beforePath)){ countDownLatch=new CountDownLatch(1); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } this.zkClient.unsubscribeDataChanges(beforePath, listener); } public void unLock() { //删除当前临时节点 zkClient.delete(currentPath); zkClient.close(); } }