手写代码模拟zookeeper分布式锁
第一部如何安装Zookeeper这里就不再累述了,网上有很多教程,自行百度一波,具体看代码 如何实现
2.1 maven工程
新建maven工程,导入zookeeper 和zkClent依赖,这里使用ZkClient客户进行操作,代码量更加精简
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.11</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.13</version> <type>pom</type> </dependency>
2.2 编写分布式锁实现类
- 定义自己的序列化协议
-
package com.xp.zookeeper; import java.io.UnsupportedEncodingException; import org.I0Itec.zkclient.exception.ZkMarshallingError; import org.I0Itec.zkclient.serialize.ZkSerializer; /** * 自定义序列化 * @author liuj * @date 2019-12-01 */ public class CustomeZkSerializer implements ZkSerializer{ String charset = "UTF-8"; public Object deserialize(byte[] bytes) throws ZkMarshallingError { try { return new String(bytes, charset); } catch (UnsupportedEncodingException e) { throw new ZkMarshallingError(e); } } public byte[] serialize(Object obj) throws ZkMarshallingError { try { return String.valueOf(obj).getBytes(charset); } catch (UnsupportedEncodingException e) { throw new ZkMarshallingError(e); } } }
这里只是简单的实现了自己的序列化协议,也可自己后期扩展,重点看分布式锁的加锁和解锁类的实现
package com.xp.zookeeper; /** * 分布式锁实现 * @author liuj * @date 2019-12-01 */ import java.util.Collections; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; public class DistributeLock implements Lock{ private String lockPath; private ZkClient zkClient; private ThreadLocal<String> currentPath = new ThreadLocal<>(); private ThreadLocal<String> beforePath = new ThreadLocal<>(); private ThreadLocal<Integer> reentCount = new ThreadLocal<>(); public DistributeLock(String lockPath){ super(); this.lockPath = lockPath; zkClient = new ZkClient("localhost:2181"); zkClient.setZkSerializer(new CustomeZkSerializer()); if(!this.zkClient.exists(lockPath)){ try{ this.zkClient.createPersistent(lockPath); }catch(Exception e){ } } } @Override public void lock() { if (!tryLock()) { waitForLock(); lock(); } } private void waitForLock(){ CountDownLatch latch = new CountDownLatch(1); IZkDataListener listener = new IZkDataListener() { @Override public void handleDataDeleted(String arg0) throws Exception { System.out.println("监听节点被删除"); latch.countDown(); } @Override public void handleDataChange(String arg0, Object arg1) throws Exception { } }; //注册当前节点上一个节点事件 this.zkClient.subscribeDataChanges(this.beforePath.get(), listener); //当前一个节点还存在的情况阻塞自己 if(this.zkClient.exists(this.beforePath.get())){ try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //取消前一个节点的注册监听事件 this.zkClient.unsubscribeDataChanges(this.beforePath.get(), listener); } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { //可重入锁逻辑实现 if(this.reentCount.get() != null){ //当前线程不是第一获取锁 int reentCount = this.reentCount.get(); if(reentCount > 0){ this.reentCount.set(++reentCount); return true; } } if(this.currentPath.get() == null){ currentPath.set(this.zkClient.createPersistentSequential(lockPath + "/","bbb")); } //获取当前节点下面所有的子节点 List<String> childList = zkClient.getChildren(lockPath); Collections.sort(childList); //判断当前节点是否最小的节点 if(currentPath.get().equals(lockPath + "/" + childList.get(0))){ return true; }else { int currentIndex = childList.indexOf(currentPath.get().substring(lockPath.length() + 1)); beforePath.set(lockPath + "/" + childList.get(currentIndex - 1)); } return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public void unlock() { if(this.reentCount.get() != null){ int reentCount = this.reentCount.get(); if(reentCount > 1){ this.reentCount.set(--reentCount); }else { this.reentCount.set(null); } } this.zkClient.delete(this.currentPath.get()); } @Override public Condition newCondition() { return null; } public static void main(String[] args) { // 并发数 int currency = 50; // 循环屏障 CyclicBarrier cb = new CyclicBarrier(currency); // 多线程模拟高并发 for (int i = 0; i < currency; i++) { new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "---------所有线程准备好---------------"); // 等待一起出发 try { cb.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } DistributeLock lock = new DistributeLock("/distLock"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + " 获得锁!"); } finally { lock.unlock(); } } }).start(); } } }
大体的思路:首先通过DistributeLock类构造方法初始化一个持久化的根节点,lock方法首先调用tryLock方法尝试在zookeeper上面创建临时节点,如果创建成功的化,就代表加锁成功,否则的化,进行调用waitForLock方法阻塞等待,在这个方法里面利用了zookeeper的wathch机制,监听上一个节点的删除时间,当当前节点的上一个节点释放锁,也就是删除了节点事件,因为这个方法首先创建一个CountDownLatch的发令枪,并且当当前节点有上一个节点的时候就会一直阻塞在这里,当监听到上一个节点删除的时候,发令枪计数器就会减一,这个时候当前节点就会被唤醒,从而lock里面就会执行当前节点的加锁逻辑,这里的思路是只注册上一个节点的的删除事件,而不是监听根节点下面所有的节点的删除事件,如果有成千上万的节点竞争锁的化,就会出现惊群效应,这里就巧妙的避免了这个问题