zookeeper分布式锁代码实现(二)

 

   之前实现的分布式锁只是利用了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?忘了。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值