算法思路:使用临时顺序(EPHEMERAL_SEQUENTIAL)节点特性
1,对于加锁操作,可以让所有客户端都去/lock目录下创建临时顺序节点,然后获取/lock节点的所有子节点,对所有子节点进行排序,
2,如果创建的客户端发现自身创建节点序列号是/lock/目录下最小的节点,则获得锁。否则,监视比自己创建节点的序列号小的节点(比自己创建的节点小的最大节点),
进入等待。
3,对于解锁操作,只需要将自身创建的节点删除即可
代码如下:
package com.cn.core;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
public class DistributeLockDemo implements Watcher {
ZooKeeper zk = null; //zookeeper原生api去实现一个分布式锁
private String root = "/locks";
private String myZonode; //表示当前获取到的锁名称-也就是节点名称
private String waitNode; // 表示当前等待的节点
private CountDownLatch latch;
//zookeeper服务 url
private static final String CONNECTION_STRING = "127.0.0.1:2181";
private static final int SESSION_TIMEOUT = 10000; //超时时间
/**
* 构造函数初始化
*
* @param config 表示zookeeper连接串
*/
public DistributeLockDemo(String config) {
try {
zk = new ZooKeeper(config, SESSION_TIMEOUT, this);
Stat stat = zk.exists(root, false); //判断是不是已经存在locks节点,不需要监听root节点
if (stat == null) { //如果不存在,则创建根节点
zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
public void process(WatchedEvent event) {
if (this.latch != null) { //如果计数器不为空话话,释放计数器锁
this.latch.countDown();
}
}
/**
* 获取锁的方法
*/
public boolean lock(String name) {
if (tryLock(name)) {
System.out.println(Thread.currentThread().getName() +"-->"+ myZonode + " - 获取 lock!");
return true;
}
try {
return waitLock(waitNode, SESSION_TIMEOUT);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
/**
* 释放锁操作的方法
*/
public void unlock() {
System.out.println(Thread.currentThread().getName()+"-->"+myZonode+"释放锁");
try {
zk.delete(myZonode, -1);
myZonode = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
private boolean tryLock(String name) {
String splitStr = name; //lock_0000000001
try {
//创建一个有序的临时节点,赋值给myznode
myZonode = zk.create(root + "/" + splitStr, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName()+"-->"+myZonode + " 创建成功");
List<String> subNodes = zk.getChildren(root, false);
Collections.sort(subNodes); //讲所有的子节点排序
if (myZonode.equals(root + "/" + subNodes.get(0))) {
//当前客户端创建的临时有序节点是locks下节点中的最小的节点,表示当前的客户端能够获取到锁
return true;
}
//否则的话,监听比自己小的节点 locks/lock_0000000003
String subMyZnode = myZonode.substring((myZonode.lastIndexOf("/") + 1));
waitNode = subNodes.get(Collections.binarySearch(subNodes, subMyZnode) - 1);// 获取比当前节点小的节点
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
private boolean waitLock(String lower, long waitTime) throws KeeperException, InterruptedException {
Stat stat = zk.exists(root + "/" + lower, true); //获取节点状态,并添加监听
if (stat != null) {
System.out.println(Thread.currentThread().getName()+"-->"+ myZonode + " waiting for" + root + " /" + lower);
this.latch = new CountDownLatch(1); //实例化计数器,让当前的线程等待
this.latch.await(waitTime, TimeUnit.MILLISECONDS);
this.latch = null;
}
System.out.println("Thread " + myZonode + "获取锁");
return true;
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
// final Semaphore semaphore = new Semaphore(10);
for (int i = 0; i < 10; i++) {
Runnable runnable = new Runnable() {
public void run() {
try {
// semaphore.acquire();
DistributeLockDemo distributeLockDemo = new DistributeLockDemo(CONNECTION_STRING);
boolean lock = distributeLockDemo.lock("product_");
if (lock) {
//业务代码
System.out.println(Thread.currentThread().getName() +"-->"+"开始执行业务代码");
// Thread.sleep(3000);
distributeLockDemo.unlock();
}
// semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
}
} ;
executorService.execute(runnable);
}
executorService.shutdown();
}
}
测试结果:
pool-1-thread-3-->/locks/product_0000000295 创建成功
pool-1-thread-6-->/locks/product_0000000296 创建成功
pool-1-thread-10-->/locks/product_0000000297 创建成功
pool-1-thread-5-->/locks/product_0000000298 创建成功
pool-1-thread-4-->/locks/product_0000000299 创建成功
pool-1-thread-7-->/locks/product_0000000300 创建成功
pool-1-thread-2-->/locks/product_0000000301 创建成功
pool-1-thread-8-->/locks/product_0000000304 创建成功
pool-1-thread-1-->/locks/product_0000000302 创建成功
pool-1-thread-9-->/locks/product_0000000303 创建成功
pool-1-thread-3-->/locks/product_0000000295 - 获取 lock!
pool-1-thread-3-->开始执行业务代码
pool-1-thread-3-->/locks/product_0000000295释放锁
pool-1-thread-4-->/locks/product_0000000299 waiting for/locks /product_0000000298
pool-1-thread-8-->/locks/product_0000000304 waiting for/locks /product_0000000303
pool-1-thread-2-->/locks/product_0000000301 waiting for/locks /product_0000000300
pool-1-thread-10-->/locks/product_0000000297 waiting for/locks /product_0000000296
pool-1-thread-7-->/locks/product_0000000300 waiting for/locks /product_0000000299
pool-1-thread-5-->/locks/product_0000000298 waiting for/locks /product_0000000297
pool-1-thread-6-->/locks/product_0000000296 waiting for/locks /product_0000000295
pool-1-thread-1-->/locks/product_0000000302 waiting for/locks /product_0000000301
pool-1-thread-9-->/locks/product_0000000303 waiting for/locks /product_0000000302
Thread /locks/product_0000000296获取锁
pool-1-thread-6-->开始执行业务代码
pool-1-thread-6-->/locks/product_0000000296释放锁
Thread /locks/product_0000000297获取锁
pool-1-thread-10-->开始执行业务代码
pool-1-thread-10-->/locks/product_0000000297释放锁
Thread /locks/product_0000000298获取锁
pool-1-thread-5-->开始执行业务代码
pool-1-thread-5-->/locks/product_0000000298释放锁
Thread /locks/product_0000000299获取锁
pool-1-thread-4-->开始执行业务代码
pool-1-thread-4-->/locks/product_0000000299释放锁
Thread /locks/product_0000000300获取锁
pool-1-thread-7-->开始执行业务代码
pool-1-thread-7-->/locks/product_0000000300释放锁
Thread /locks/product_0000000301获取锁
pool-1-thread-2-->开始执行业务代码
pool-1-thread-2-->/locks/product_0000000301释放锁
Thread /locks/product_0000000302获取锁
pool-1-thread-1-->开始执行业务代码
pool-1-thread-1-->/locks/product_0000000302释放锁
Thread /locks/product_0000000303获取锁
pool-1-thread-9-->开始执行业务代码
pool-1-thread-9-->/locks/product_0000000303释放锁
Thread /locks/product_0000000304获取锁
pool-1-thread-8-->开始执行业务代码
pool-1-thread-8-->/locks/product_0000000304释放锁
扩展:
menagerie
其实就是对以上思路的一个封装,不用自己写代码了。直接拿来用就可以了。
menagerie基于Zookeeper实现了java.util.concurrent包的一个分布式版本。这个封装是更大粒度上对各种分布式一致性使用场景的抽象。其中最基础和常用的是一个分布式锁的实现: org.menagerie.locks.ReentrantZkLock,通过ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL类型znode的支持,实现了分布式锁。具体做法是:不同的client上每个试图获得锁的线程,都在相同的basepath下面创建一个EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要创建的是临时znode,创建连接断开时会自动删除; SEQUENTIAL表示要自动在传入的path后面缀上一个自增的全局唯一后缀,作为最终的path。因此对不同的请求ZK会生成不同的后缀,并分别返回带了各自后缀的path给各个请求。因为ZK全局有序的特性,不管client请求怎样先后到达,在ZKServer端都会最终排好一个顺序,因此自增后缀最小的那个子节点,就对应第一个到达ZK的有效请求。然后client读取basepath下的所有子节点和ZK返回给自己的path进行比较,当发现自己创建的sequential node的后缀序号排在第一个时,就认为自己获得了锁;否则的话,就认为自己没有获得锁。这时肯定是有其他并发的并且是没有断开的client/线程先创建了node。
menagerie地址:https://github.com/openUtility/menagerie