unLock()方法的代码如下:
/** * 释放锁 * * @return 是否成功释放锁 */ @Override public boolean unlock() { //只有加锁的线程,能够解锁 if (!thread.equals(Thread.currentThread())) { return false; } //减少可重入的计数 int newLockCount = lockCount.decrementAndGet(); //计数不能小于0 if (newLockCount < 0) { throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + locked_path); } //如果计数不为0,直接返回 if (newLockCount != 0) { return true; } //删除临时节点 try { if (ZKclient.instance.isNodeExist(locked_path)) { client.delete().forPath(locked_path); } } catch (Exception e) { e.printStackTrace(); return false; } return true; }这里,为了尽量保证线程安全,可重入计数器的类型,使用的不是int类型,而是Java并发包中的原子类型——AtomicInteger。
实战:分布式锁的使用写一个用例,测试一下ZLock的使用,代码如下:
@Test public void testLock() throws InterruptedException { for (int i = 0; i < 10; i++) { FutureTaskScheduler.add(() -> { //创建锁 ZkLock lock = new ZkLock(); lock.lock(); //每条线程,执行10次累加 for (int j = 0; j < 10; j++) { //公共的资源变量累加 count++; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("count = " + count); //释放锁 lock.unlock(); }); } Thread.sleep(Integer.MAX_VALUE); }以上代码是10个并发任务,每个任务累加10次,执行以上用例,会发现结果会是预期的和100,如果不使用锁,结果可能就不是100,因为上面的count是一个普通的变量,不是线程安全的。
说 明
有关线程安全的核心原理和实战知识,请参阅本书的下一卷《Java高并发核心编程(卷2)》。
原理上一个Zlock实例代表一把锁,并需要占用一个Znode永久节点,如果需要很多分布式锁,则也需要很多的不同的Znode节点。以上代码,如果要扩展为多个分布式锁的版本,还需要进行简单改造,这种改造留给各位自己去练习和实现吧。
实战:curator的InterProcessMutex 可重入锁分布式锁Zlock自主实现主要的价值:学习一下分布式锁的原理和基础开发,仅此而已。实际的开发中,如果需要使用到分布式锁,并建议去自己造轮子,建议直接使用Curator客户端中的各种官方实现的分布式锁,比如其中的InterProcessMutex
可重入锁。
这里提供一个简单的InterProcessMutex 可重入锁的使用实例,代码如下:
@Test public void testzkMutex() throws InterruptedException { CuratorFramework client = ZKclient.instance.getClient(); final InterProcessMutex zkMutex = new InterProcessMutex(client, "/mutex"); ; for (int i = 0; i < 10; i++) { FutureTaskScheduler.add(() -> { try { //获取互斥锁 zkMutex.acquire(); for (int j = 0; j < 10; j++) { //公共的资源变量累加 count++; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("count = " + count); //释放互斥锁 zkMutex.release(); } catch (Exception e) { e.printStackTrace(); } }); } Thread.sleep(Integer.MAX_VALUE); } ZooKeeper分布式锁的优点和缺点总结一下ZooKeeper分布式锁:
(1)优点:ZooKeeper分布式锁(如InterProcessMutex),能有效的解决分布式问题,不可重入问题,使用起来也较为简单。
(2)缺点:ZooKeeper实现的分布式锁,性能并不太高。为啥呢?
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。大家知道,ZK中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器还需要将数据同不到所有的Follower机器上,这样频繁的网络通信,性能的短板是非常突出的。
总之,在高性能,高并发的场景下,不建议使用ZooKeeper的分布式锁。而由于ZooKeeper的高可用特性,所以在并发量不是太高的场景,推荐使用ZooKeeper的分布式锁。
在目前分布式锁实现方案中,比较成熟、主流的方案有两种:
(1)基于Redis的分布式锁
(2)基于ZooKeeper的分布式锁
两种锁,分别适用的场景为:
(1)基于ZooKeeper的分布式锁,适用于高可靠(高可用)而并发量不是太大的场景;
(2)基于Redis的分布式锁,适用于并发量很大、性能要求很高的、而可靠性问题可以通过其他方案去弥补的场景。
总之,这里没有谁好谁坏的问题,而是谁更合适的问题。