之前实现的分布式锁只是利用了zookeeper的临时节点,在大集群的环境下并不适用,会出现“惊群”效应:每次节点删除,所有的调用者都来获取锁,zookeeper负载太大,也造成资源不必要的浪费;这时可以为调用者定一个顺序(zookeeper的临时顺序节点),当调用者自己的编号是所有节点中最小的,那设定它取得了锁,否则监听最小的节点,这个节点删除时,重新尝试获得锁。
本例中使用了ThreadLocal,它并不是把数据存储在它内部,它只是作为一个key,把真实数据放在Thread的一个Map<ThreadLocal, Object>里的,get的时候也是从这个map里取, 它是操作线程副本变量的中介。详见
Java并发编程:深入剖析ThreadLocal
-
/**
-
* zookeeper锁实现(临时顺序结点)
-
* @author skymr
-
*
-
*/
-
public class ZookeeperLock1 implements Lock, Watcher{
-
public ZookeeperLock1(String url, int sessionTimeOut, String path){
-
this.parrentPath = path;
-
try {
-
//url是zookepper服务器的地址
-
zk = new ZooKeeper(url, sessionTimeOut, this);
-
latch.await();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
//zk客户端
-
private ZooKeeper zk;
-
//结点路径
-
private String parrentPath;
-
//用于初始化zk的,zk连接是异步的,但连接成功后才能进行调用
-
private CountDownLatch latch = new CountDownLatch(1);
-
private static ThreadLocal<String> currentNodePath = new ThreadLocal<String>();
-
public void lock() {
-
if(!tryLock()){
-
String mypath = currentNodePath.get();
-
//如果尝试加锁失败,则进入等待
-
synchronized(mypath){
-
System.out.println(Thread.currentThread().getName() +" lock失败,进入等待");
-
try {
-
mypath.wait();
-
} catch (Exception e) {
-
}
-
System.out.println(Thread.currentThread().getName() +" lock等待完成");
-
}
-
//等待别人释放锁后,自己再去加锁
-
lock();
-
}
-
else{
-
System.out.println(Thread.currentThread().getName() +" lock成功");
-
}
-
}
-
public void lockInterruptibly() throws InterruptedException {
-
}
-
public boolean tryLock() {
-
try {
-
//加锁代码是创建一个节点
-
String mypath = currentNodePath.get();
-
if(mypath == null){
-
mypath = zk.create(parrentPath + "/", "111".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
-
currentNodePath.set(mypath);
-
}
-
final String currentPath = mypath;
-
List<String> allNodes = zk.getChildren(parrentPath, false);
-
Collections.sort(allNodes);
-
//不抛异常就表示创建成功啦
-
String nodeName = mypath.substring((parrentPath + "/").length());
-
if(allNodes.get(0).equals(nodeName)){
-
//当前结点是最小的节点,获取锁成功
-
return true;
-
}
-
else{
-
//监听最小的结点
-
String targetNodeName = parrentPath + "/" + allNodes.get(0);
-
System.out.println(Thread.currentThread().getName() +" 需要等待节点删除" + targetNodeName);
-
zk.exists(targetNodeName, new Watcher() {
-
public void process(WatchedEvent event) {
-
if(event.getType() == EventType.NodeDeleted){
-
synchronized(currentPath){
-
currentPath.notify();
-
}
-
System.out.println(Thread.currentThread().getName() +" 通知Lock等待中的线程重试加锁");
-
}
-
}
-
});
-
}
-
return false;
-
} catch (Exception e) {
-
return false;
-
}
-
}
-
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
-
return false;
-
}
-
public void unlock() {
-
try {
-
//释放锁,删除节点
-
String mypath = currentNodePath.get();
-
if(mypath != null){
-
System.out.println(Thread.currentThread().getName() +" 释放锁");
-
zk.delete(mypath, -1);
-
currentNodePath.remove();
-
}
-
} catch (Exception e) {
-
}
-
}
-
public Condition newCondition() {
-
return null;
-
}
-
public void process(WatchedEvent event) {
-
System.out.println(event);
-
if(event.getType() == EventType.None){
-
//当连接上了服务器后,初始化完成
-
latch.countDown();
-
}
-
}
-
}
说明:
trylock方法中首先检查线程副本变量中,是否存在zk节点,若存在,表示当前线程已创建结点,不能再创建结点了,此次调用只是为了看看是否获取了锁。
在父结点中找到最小的结点,如果最小结点就是自己创建的结点,表示获取锁成功,否则监听最小结点
如果监听的结点被删除,收到 event的类型为NodeDeleted,则通知正在监听此结点的其他结点,让其退出等待
lock方法:尝试加锁,加锁失败进入等待,等待完成后重试加锁。
wait方法是针对当前结点名称调用,对当前结点名称进行synchronized,而不是用zk对象,是因为在同一个进程的不同线程使用同一个zookeeper客户端时(不同线程共享ZookeeperLock1对象), 如果对zk进行notifyAll的话,那所有调用者线程都被唤起,都去重新尝试获取锁,影响性能。在同一服务器中同一进程使用同一个ZookeeperLock1对象,即不同线程使用的是同一个zk客户端,节省了zk服务器的资源,如果每个线程都单独有个zk连接,那zk服务器维护的连接数量太大。
释放所的时候把线程副本变量中的结点名称删除掉,一是为了节省内存空间,二是清除锁数据,如果不清除,这条数据存在的时间一长,等下次同一线程进行lock的时候就不会去重新create节点了,使用的是以前的节点,但这个节点在zk中又不存在,会引发重大问题。
测试:
-
public class LockTest {
-
// private static Lock lock = new ReentrantLock();
-
private static Lock lock = new ZookeeperLock1("localhost", 3000, "/node");
-
public static void main(String[] args) throws Exception{
-
for(int i = 0; i < 10; i++){
-
new Thread(){
-
public void run(){
-
// Lock lock = new ZookeeperLock1("localhost", 3000, "/node");
-
try{
-
lock.lock();
-
System.out.println(Thread.currentThread().getName() + "开始执行");
-
try {
-
Thread.sleep(100);
-
} catch (InterruptedException e) {
-
}
-
System.out.println(Thread.currentThread().getName() + "执行完成 ");
-
}
-
finally{
-
lock.unlock();
-
}
-
}
-
}.start();
-
}
-
}
-
}
测试结果:
-
WatchedEvent state:SyncConnected type:None path:null
-
Thread-5 需要等待节点删除/node/0000000021
-
Thread-9 需要等待节点删除/node/0000000021
-
Thread-1 需要等待节点删除/node/0000000021
-
Thread-4 需要等待节点删除/node/0000000021
-
Thread-0 需要等待节点删除/node/0000000021
-
Thread-2 需要等待节点删除/node/0000000021
-
Thread-3 需要等待节点删除/node/0000000021
-
Thread-7 需要等待节点删除/node/0000000021
-
Thread-6 lock成功
-
Thread-6开始执行
-
Thread-8 需要等待节点删除/node/0000000021
-
Thread-6执行完成
-
Thread-6 释放锁
-
Thread-7 lock失败,进入等待
-
Thread-3 lock失败,进入等待
-
Thread-2 lock失败,进入等待
-
Thread-8 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-0 lock失败,进入等待
-
Thread-4 lock失败,进入等待
-
Thread-1 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-3 lock等待完成
-
Thread-2 lock等待完成
-
Thread-7 lock等待完成
-
Thread-9 lock等待完成
-
Thread-0 lock等待完成
-
Thread-4 lock等待完成
-
Thread-5 lock等待完成
-
Thread-1 lock等待完成
-
Thread-8 lock等待完成
-
Thread-3 需要等待节点删除/node/0000000022
-
Thread-2 需要等待节点删除/node/0000000022
-
Thread-7 需要等待节点删除/node/0000000022
-
Thread-9 需要等待节点删除/node/0000000022
-
Thread-2 lock失败,进入等待
-
Thread-3 lock失败,进入等待
-
Thread-4 需要等待节点删除/node/0000000022
-
Thread-1 需要等待节点删除/node/0000000022
-
Thread-7 lock失败,进入等待
-
Thread-0 需要等待节点删除/node/0000000022
-
Thread-8 lock成功
-
Thread-8开始执行
-
Thread-5 需要等待节点删除/node/0000000022
-
Thread-1 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
Thread-4 lock失败,进入等待
-
Thread-0 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-8执行完成
-
Thread-8 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-7 lock等待完成
-
Thread-3 lock等待完成
-
Thread-2 lock等待完成
-
Thread-1 lock等待完成
-
Thread-0 lock等待完成
-
Thread-4 lock等待完成
-
Thread-9 lock等待完成
-
Thread-5 lock等待完成
-
Thread-7 需要等待节点删除/node/0000000023
-
Thread-3 lock成功
-
Thread-3开始执行
-
Thread-2 需要等待节点删除/node/0000000023
-
Thread-7 lock失败,进入等待
-
Thread-0 需要等待节点删除/node/0000000023
-
Thread-4 需要等待节点删除/node/0000000023
-
Thread-9 需要等待节点删除/node/0000000023
-
Thread-1 需要等待节点删除/node/0000000023
-
Thread-2 lock失败,进入等待
-
Thread-5 需要等待节点删除/node/0000000023
-
Thread-0 lock失败,进入等待
-
Thread-4 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
Thread-1 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-3执行完成
-
Thread-3 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-4 lock等待完成
-
Thread-5 lock等待完成
-
Thread-9 lock等待完成
-
Thread-2 lock等待完成
-
Thread-7 lock等待完成
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-1 lock等待完成
-
Thread-0 lock等待完成
-
Thread-4 需要等待节点删除/node/0000000024
-
Thread-5 需要等待节点删除/node/0000000024
-
Thread-9 需要等待节点删除/node/0000000024
-
Thread-2 需要等待节点删除/node/0000000024
-
Thread-7 需要等待节点删除/node/0000000024
-
Thread-0 需要等待节点删除/node/0000000024
-
Thread-4 lock失败,进入等待
-
Thread-1 lock成功
-
Thread-1开始执行
-
Thread-5 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
Thread-2 lock失败,进入等待
-
Thread-7 lock失败,进入等待
-
Thread-0 lock失败,进入等待
-
Thread-1执行完成
-
Thread-1 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-2 lock等待完成
-
Thread-7 lock等待完成
-
Thread-5 lock等待完成
-
Thread-4 lock等待完成
-
Thread-9 lock等待完成
-
Thread-0 lock等待完成
-
Thread-2 需要等待节点删除/node/0000000025
-
Thread-7 lock成功
-
Thread-7开始执行
-
Thread-0 需要等待节点删除/node/0000000025
-
Thread-2 lock失败,进入等待
-
Thread-5 需要等待节点删除/node/0000000025
-
Thread-4 需要等待节点删除/node/0000000025
-
Thread-9 需要等待节点删除/node/0000000025
-
Thread-0 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-4 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
Thread-7执行完成
-
Thread-7 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-2 lock等待完成
-
Thread-0 lock等待完成
-
Thread-9 lock等待完成
-
Thread-4 lock等待完成
-
Thread-5 lock等待完成
-
Thread-2 需要等待节点删除/node/0000000026
-
Thread-0 lock成功
-
Thread-0开始执行
-
Thread-9 需要等待节点删除/node/0000000026
-
Thread-4 需要等待节点删除/node/0000000026
-
Thread-5 需要等待节点删除/node/0000000026
-
Thread-2 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
Thread-4 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-0执行完成
-
Thread-0 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-2 lock等待完成
-
Thread-4 lock等待完成
-
Thread-5 lock等待完成
-
Thread-9 lock等待完成
-
Thread-2 lock成功
-
Thread-2开始执行
-
Thread-4 需要等待节点删除/node/0000000027
-
Thread-5 需要等待节点删除/node/0000000027
-
Thread-9 需要等待节点删除/node/0000000027
-
Thread-4 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-9 lock失败,进入等待
-
Thread-2执行完成
-
Thread-2 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-9 lock等待完成
-
Thread-5 lock等待完成
-
Thread-4 lock等待完成
-
Thread-9 需要等待节点删除/node/0000000028
-
Thread-5 需要等待节点删除/node/0000000028
-
Thread-4 lock成功
-
Thread-4开始执行
-
Thread-9 lock失败,进入等待
-
Thread-5 lock失败,进入等待
-
Thread-4执行完成
-
Thread-4 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-9 lock等待完成
-
Thread-5 lock等待完成
-
Thread-9 lock成功
-
Thread-9开始执行
-
Thread-5 需要等待节点删除/node/0000000029
-
Thread-5 lock失败,进入等待
-
Thread-9执行完成
-
Thread-9 释放锁
-
main-EventThread 通知Lock等待中的线程重试加锁
-
Thread-5 lock等待完成
-
Thread-5 lock成功
-
Thread-5开始执行
-
Thread-5执行完成
-
Thread-5 释放锁
最近wait/notify机制好像是被什么机制替代了呢,pack/upack?忘了。
转载地址:https://blog.csdn.net/naruto_mr/article/details/81506065