Redisson的CountDownLatch实现对比 VS Java的CountDownLat

王道革 2020-04-11

JDK的CountDownLatch的设计思路
和锁类似,Java实现CountDownLatch的时候也利用了一个叫做AQS的东西,源码参照java.util.concurrent.locks.AbstractQueuedSynchronizer。

这个类的其中一个思想核心是内部的一个state状态变量,这个状态会告诉你当前的锁是否可用,当前的latch是否倒计时结束等等。

核心接口连个方法,一个是countDown,另一个是await,countDown是改变状态的,await可以类比对锁的等待。

先看countDown
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
这里使用自旋的方式,原子性改变状态,如果改变成功就返回true。注意这个latch是不可重复使用的,所以如果状态是0的话就无法再使用了。而doReleaseShared是为了唤醒所有await该latch的线程,具体的说明可以参照https://blog.csdn.net/xxcupid/article/details/51909921。

Redisson版本的CountDownLatch设计思路
和Redisson实现分布式锁类似,需要利用到Redis的订阅/发布来实现通知的操作,来唤醒所有await当前latch的所有线程。

这里有一个叫做ReclosableLatch的类继承了AQS,

Redisson的CountDownLatch实现对比 VS Java的CountDownLat
看下和JDK的CountDownLatch的区别:
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
Redisson在release的时候并没有采用自旋+原子操作修改状态,这是为什么呢?因为真正控制count down的操作是redis远端实现的,所以这里的latch类似一个信号量的概念,只不过该信号量通知的所有等待的线程,而不是像fair锁那样通知一个线程,所以这里不需要原子操作,当Redis发现当前count down计数到0,会发布一个消息,
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
这个消息会被CountDownLatchPubSub订阅到(这里是分布式的消息,所以多个虚拟机都可以订阅到这个消息,实现了分布式的CountDownLatch功能),然后调用open方法,唤醒所有在await的线程,
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
这里有两个状态,OPEN和CLOSED两个状态,OPEN对应的是普通CountDownLatch的计数为0的状态,即能获取到锁的状态。Redisson的初始状态是CLOSED的状态,所以await的线程都会被hang住。

可以通过trySetCount设置初始count值,同时发布close消息,latch准备就绪。

Redisson的CountDownLatch方法分析
await方法:
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
这个方法比较好理解,先进行订阅操作,然后轮询遍历等待该latch处于open状态,这里会利用ReclosableLatch阻塞,当Redis发布消息的时候,无论是open还是close,都会执行release操作唤醒该await的阻塞, 然后继续检验getCount的值,如果处于OPEN状态了(等于0),那么说明阻塞解除,同时移除对该消息的订阅。

带超时时间的await

Redisson的CountDownLatch实现对比 VS Java的CountDownLat
和不带超时时间的方法相比,就是要对时间进行校验,如果超过过期时间了,那么就返回失败。

这里的第一个if没太看懂,执行的是CommandAsyncExecutor的await操作,这里失败除了超时之外还有别的原因吗?因为后面会检查失败之后,是否还要继续等待,那就是说明还有一种情况,即使失败了,也没有超时?

如果第一个await失败了,但是还没有到超时时间,那么继续和上面方法一样的逻辑,用ReclosableLatch阻塞,只不过这个阻塞是带超时时间的阻塞,如果超时了就返回false,否则继续轮训校验。

countDownAsync方法
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
这里是一段lua脚本,逻辑如下:

首先对指定key执行decr操作(在key不存在的时候,会先初始化该key值为0,然后减一,即-1),如果v不是正数,那么删除该key,如果v是0,那么在删除key操作之后,还要发布一个zero的消息,将该CountDownLatch设置为OPEN状态。

所以当key不能存在的时候,相当于什么都没做,因为对key初始化并减一操作之后又将该key擦除掉了,只有当Redis中存有该key,即该latch被初始化后,才有作用。

这里和JDK逻辑有个不同之处,JDK的CountDownLatch在做count down的时候,是原子操作该state,但是这里不需要,因为lua脚本本身保证了原子性,当前

trySetCountAsync方法
Redisson的CountDownLatch实现对比 VS Java的CountDownLat
如果当前key不存在,那么就初始化该key的次数,然后发布newCount消息,这个消息会将本地的latch状态设置为CLOSE。如果key存在,那么不做操作,即在latch初始化生效之后,不允许覆盖当前已存在的latch的count值。

相关推荐