赞
踩
数据库和redis也可实现分布式锁,本文只将三者做一个对比,不实现数据库和redis,将实现ZK的分布式锁
package cn.enjoy.zk; /* * @author wangle25 * @description lock接口 * @date 20:38 2020-07-12 * @param * @return **/ public interface Lock { //获取到锁的资源 public void getLock(); // 释放锁 public void unLock(); }
package cn.enjoy.zk; /* * @author wangle25 * @description 抽象接口 * @date 20:39 2020-07-12 * @param * @return **/ 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(); }
package cn.enjoy.zk; import org.I0Itec.zkclient.ZkClient; /** * @author wangle25 * @description ,zk分布式锁抽象类,将重复代码写入子类中,模版方法 * @date 20:43 2020-07-12 * @param * @return **/ 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"; }
package scmp.portal.zkLock; import org.I0Itec.zkclient.IZkDataListener; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; /* * @author wangle25 * @description 基于zk的分布式锁的实现 * @date 20:21 2020-07-12 * @param * @return **/ 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; } // 如果当前节点在所有节点中排名中不是排名第一,则获取前面的节点名称,并赋值给beforePath else { 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(); } } }; // 给排在前面的的节点增加数据删除的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(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。