上一篇,我们基于示例和源码去剖析了可重入互斥锁,不可重入互斥锁,信号量锁,参见【画分布式锁之"通文馆圣主"Curator的"十三太保"(上) 】,Curator圣主的强大,Zookeeper的健壮性,都让zk分布式锁不可匹敌,本文将继续分析剩余的分布式锁,让我们一起江湖里过招吧。
【勇,联合锁 && 加锁 && 释放锁】
联合锁,multiLock,这个我们之前也是分析过的,申请多个小锁,合并成一个大锁,并且要保证这多个小锁都要在规定时间内要加锁成功,才算加锁成功,进行业务逻辑处理,最后也是要依次释放所有的锁。
这个锁借助的还是一开始讲的互斥锁,加锁流程并不复杂,我们来看看源码,其实就是对三把锁进行遍历,每一把都单独加锁,每次加锁成功就会放入一个list中,一旦出现一个锁加
锁失败,那么就会对已经加锁成功的几把锁,都进行释放操作。释放操作呢,就更简单了,就是对所有已经加锁成功的锁进行遍历,将所有的锁的一一释放,把对应的节点删除就OK了。到这一把锁,还是很easy的。
【忍,读写锁 && 加锁 && 释放锁】
读写锁的分析呢,之前也分析过,无非是要去分析读写,读读,写写,写读之前是否互斥,能否重入。我们一点点分析,相信经过多次分析,对这些也是轻车熟路,go~go~。
【读写锁 && 读读】
首先当我们创建一个读写锁对象InterProcessReadWriteLock的时候,就会实例化一个读锁对象和一个写锁对象,InternalInterProcessMutex我们可以看出,这个对象是继承于InterProcessMutex互斥锁对象,并且重写了一些方法。那我们先来看自读锁对象,这个对象也是继承于互斥锁,重写了一些方法,一开始的节奏和互斥锁一样,我们一步步往下看,看看哪里发生了变化,关键的点在哪里。
我们跟着加锁方法【 acquire() 】debug,前面的流程和互斥锁完全一样,先从本地缓存map中看看有没有当前线程对应的锁信息对象,然后就是就是调用【attemptLock()】方法去尝试获取锁,一堆变量赋值之后,进入到无限循环,只是这里我们将基于path="/locks/lock_01/__READ__"创建节点,得到的ourPath=/locks/lock_01/_c_92805df7-1f14-41c7-ab8b-86b3f66577dd-__READ__0000000025,显而易见,这是读相关的一个节点。接着调用【internalLockLoop()】方法,还是进入一个while的无限循环,先是【 getSortedChildren】获取排完序的子节点列表,此时节点就是当前025节点,然后就调用了【 getsTheLock 】方法,这里我们就会发现它走的是子类实现的方法,不是原来互斥锁的方法了。
首先,if判断了当前线程是不是持有写锁,这个我们后面分析,我们接着往下看,有几个关键变量我们需要重点关注一下,【 index=0,firstWriteIndex=2147483647,ourIndex=2147483647】,遍历之前的children列表,先是判断是否包含写标识“_WRIT_”,这里肯定不是,判断节点路径是否包含sequenceNodeName=_c_e54fe0fa-185f-4802-9c38-ae3b7cc0d131-__READ__0000000030,如果包含,就执行ourIndex=index,然后break;其实就是定位一下我们传过来的节点在children中的角标位置。ourIndex=index=0;
紧接着,校验一波ourIndex有没有问题,如果ourIndex小于0,就抛出异常;好,关键的判断在这里【boolean getsTheLock=(ourIndex < firstWriteIndex)】,这里0<2147483647,成立,就封装结果对象返回了。一路返回,获取锁成功。并且将锁和当前线程绑定起来,存到本地缓存中。
那么,假设当前已经有一个客户端线程持有锁了,另一个线程来获取读锁呢?我们可以根据上面的流程来梳理一下,之前最核心的判断就是ourIndex是否小于firstWriteIndex,此时的firstWriteIndex等于Integer.MaxValue,相当于这么多客户端同时加锁是没有问题的,结论:【读读不互斥,多个客户端加读锁不互斥,最多可以有2147483647个客户端同时加读锁】。
【读写锁 && 读写】
那么当前有客户端获取了读锁,另一个客户端来获取写锁呢?我们就要来看看加写锁的流程。前面的加锁流程都是一样的,基于path=/locks/lock_01/__WRIT__,创建节点,获得ourPath=/locks/lock_01/_c_ef3510d2-56a1-4f43-a9b8-212ddf4e3dcc-__WRIT__0000000033,一路往下走,到达【getsTheLock】方法,我们可以看到这样一个关键判断【 ourIndex 】,此时呢,我们的写节点在列表的第二个,他的前面有一个读锁,那么ourIndex=1读写互斥,当已经有客户端加读锁的情况下,写锁将加锁失败,直到所有的读锁都释放,才可能加写锁成功。】
【 读写锁 && 写读】
再来看看当一个客户端加了写锁的情况下,加读锁会不会互斥?还记得上文我们分析读锁的加锁过程中,下面这个判断语句吗?当当前的线程
持有写锁的时候,是直接返回加锁成功的,那么在这种情况下,加读锁是没有问题的。如果这个判断不成立,不是当前线程持有写锁,我们接着往下看,还是关注这几个关键的变量,此时呢第一个节点是写锁【firstWriteIndex=index=0,ourIndex=i++=1】,在接下来的判断中可
以看到【ourIndex=1<firstWriteIndex=0】不成立,这句话也说明了有写锁的时候不是同一个客户端线程就不能加读锁,那么接下来就会继续走监听前一个节点,等待前一个节点释放,【注意的是这里的节点是写锁节点】,等待加锁的流程。当后面有很多客户端要加读锁也是一样的,要排队等待写锁节点释放,才会加锁成功,结论:【当 当前客户端线程持有写锁时,当前客户端线程加读锁不互斥,其他客户端线程加读锁互斥。】
【读写锁 && 写写】
如果一个客户端加了写锁,另一个客户端来加写锁,又当如何呢?回想一下上文说的写锁加锁流程,此时我们尝试加锁的节点在children列表的第二个,不是第一位,【 ourIndex <maxLeases】,1<1不成立,所以加锁失败,也必须要等前一个节点释放,前一个写锁释放,第二个写锁才能加锁成功。结论:【读写锁,写写互斥】。
到这里,我们就把Curator圣主门下的分布式锁系列大概说全了,江湖里需要用这些手段去历练一番,才能得心应手,Curator的能力不止这些,还有很多核心能力值得我们去探索,今天就写到这里啦,夜已深了,最近的流感好像有人心惶惶了,每个小伙伴都要呵护身体,为自己和家人留一个好身体。文中有什么问题和错误,欢迎留言指正,批评,谢谢大家,晚安。
网友评论