架构师课程的分布式锁,在我看来有问题呀,老师您看看呢

来源:9-5 开发分布式锁

Panda_io

2021-03-02

老师好呀,我从架构师课程过来的,架构师老师讲分布式锁的时候我觉得有些问题,想这边请教下最帅气的风间老师

先上代码(代码来自架构师课程的源码)

Slf4j
public class ZkLock implements AutoCloseable , Watcher {
	private ZooKeeper zooKeeper;
	private String znode;

	public ZkLock() throws IOException {
		this.zooKeeper = new ZooKeeper("192.168.179.106:2181",
				30000,this);
	}

	public boolean getLock(String businessCode) {
		try {
			System.out.println(Thread.currentThread().getName());
			// 创建业务根节点
			Stat stat = zooKeeper.exists("/" + businessCode, false);
			if (stat == null) {
				zooKeeper.create("/" + businessCode,
						businessCode.getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE,
					CreateMode.PERSISTENT);
		}
			// 创建瞬时有序节点/order/order_0000001
			znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_",
					businessCode.getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE,
					CreateMode.EPHEMERAL_SEQUENTIAL);

			// 获取业务节点下面的所有子节点
			List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode,false);
			// 子节点按升序进行排序
			Collections.sort(childrenNodes);

			//获取序号最小的(第一个)子节点
			String firstNode = childrenNodes.get(0);

			// 如果创建的节点是第一个子节点,则获得锁
			if (znode.endsWith(firstNode)) {
				return true;
			}

			// 不是第一个子节点则监听前一个节点
			String lastNode = null;
			for (String node : childrenNodes) {
				if (znode.endsWith(node)) {
					Stat exist = zooKeeper.exists("/" + businessCode + "/" + lastNode,true);
					System.out.println(exist == null);
					break;
				} else {
					lastNode = node;
				}
			}

			synchronized (this) {
				wait();
			}

			return true;

		} catch (KeeperException | InterruptedException e) {
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public void close() throws Exception {
		zooKeeper.delete(znode,-1);
		zooKeeper.close();
		log.info("我已经释放了锁");
	}

	@Override
	public void process(WatchedEvent watchedEvent) {
		if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
			synchronized (this) {
				notify();
			}
		}
	}
}

但是我发现了一个问题,那边老师回答含糊不清
首先进行打断点调试,在获取序号最小的子节点这里打上断点

图片描述

此时进来两个线程exex-1 exec-2

由于是两个线程,那么肯定会在zk上创建两个数据节点000 和 0001

图片描述

我们看这两个线程获取的子节点信息

exec-1,此时它只查询到了一个节点000,原因是它先到断点这里

图片描述

再看exec-2,它查询到两个节点,原因就是它后到,它自己有添加了一个临时节点,所以查出来两个也就是线程1的000和自己创建的001

图片描述

好了,老师这里是关键,我让exec-1这个线程先执行,由于该线程查询来的节点只有一个且与自己的序列号相等,那么它直接获取到锁然后return true,跳出try的时候关闭连接然后删除000节点,000节点被删除了哈!!!!!!!!!!!

图片描述

如果你不相信被删除,我们看zk,只有001了

图片描述

紧接着放开exec-2线程,由于它里面有两个child节点 000(已经被exec-1删除了) 以及0001

图片描述

按照逻辑找到000,然后给它设置监听器,删除了的节点设置监听器是无效的,此时返回的是null !!!,既然都无效了(节点早就被exec-1删除了)谁去触发这个事件?很明显这个监听器没设置成功嘛,就算设置成功也没有线程再去删除000,它早就被删除了嘛

图片描述

接下来按照逻辑进入wait

图片描述

这个请求将永远陷入wait

图片描述

写回答

1回答

风间影月

2021-03-03

看你的描述貌似没毛病。这个程序也不一定,你在线程的地方wait看看吧,问下那个老师。我觉得应该问题不大的。

0
1
Panda_io
那个老师说加超时时间,我感觉他描述得不太准确 但是我觉得最好的办法是,判断添加监听失败就返回true,添加监听失败exist返回null 就代表节点不存在了,既然不存在了,那么它前面那个线程肯定执行完成都释放锁了。使用wait(time)的话,如果时间太短可能前面线程还没执行完成,第二个线程就获取到锁了,也就是同时存在两把锁,不符合要求,时间太长的话,长时间得不到唤醒导致程序并发性能又太低,您觉得呢老师? // 不是第一个子节点则监听前一个节点 String lastNode = null; for (String node : childrenNodes) { if (znode.endsWith(node)) { Stat exist = zooKeeper.exists("/" + businessCode + "/" + lastNode,true); if (exist == null) { return true; } break; } else { lastNode = node; } }
2021-03-03
共1条回复

ZK分布式专题与Dubbo微服务入门,成长与加薪必备

进阶中高级工程师必备技能,大数据与微服务最常用的中间件

1859 学习 · 321 问题

查看课程