ReentrantLock
公平锁和非公平锁相同之处
都会使用到等待队列,并且队列中,先来的线程会优先获取到锁资源。所以其实就算是非公平锁,等待队列中的线程也还是公平的。
排队队列,使用的是双向链表的结构。
公平锁和非公平锁不同之处
hasQueuedPredecessors 实现的效果是。如果等待队列中有值,那么尝试当前线程,不能直接去获取锁资源。需要加入等待队列中排队。
结论:reentrantLock的非公平锁,对于等待中的线程也是有个公平的机制的,只是没有控制新来的线程(无锁状态下,可以直接尝试获取锁)。而公平锁解决了这个问题(无锁状态下,也不能直接获取锁,需要加入等待队中排队;除非等待队列为空,才能直接尝试获取)。
补充下:
ReentrantLock获取锁的方法有好几个,每个都有一点小差别,但是整体实习思路查不到。
lock方法:会一直尝试获取锁资源。
trylock方法不加时间:只会尝试一次获取锁资源。拿不到,就返回失败。没有使用到队列。
trylock加时间:一段时间内获取不到锁资源会失败。如果截止时间到现在的时间是大于1微秒的,则线程休眠(基本上就是休眠了),否则继续尝试获取。
读锁和写锁的底层实现
首先明确下读写锁的实现效果:
读读不互斥;读写互斥;
写写互斥;写读互斥;
总结:读写场景互斥。但是读场景下的读操作不互斥,写场景下的写操作之间互斥。
实现原理:
读锁使用的共享锁;写锁使用的是排他锁。
正常情况下,共享锁和排他锁不能同时存在,这样就可以实现读写场景互斥。(但是如果是同一线程,同时持有读写锁是可以的)
共享锁,允许多个线程同时持有。可以实现,读场景下的读线程之间不互斥的效果。
排他锁,不允许多个线程同时持有。可以实现,写场景下的写线程之间互斥的效果。
下面可以看看代码是如何实现的。
如果不能获取到锁资源会怎么处理?
和上面一样。读写锁也支持公平和非公平两种模式。
redisson
1. redisson如何实现尝试获取锁的逻辑
- 如何实现在一段的时间内不断的尝试获取锁
- 其实就是搞了个while循环,不断的去尝试获取锁资源。但是因为latch的存在会在给定的时间内处于休眠状态。
- 这个事件,监听的是解锁动作,如果解锁动作发生。会调用latch.release方法,这样while循环又可以重新启动,去尝试获取锁资源了。(相比单纯的轮训,避免了对cpu资源的浪费。通过信号通知,避免了没必要的轮训)
- 尝试获取锁的过程是怎样的?
- 使用了redis脚本执行的方式。因为存在根据查询结果,来决定执行什么变更动作。所以一定要保证动作串行执行。如果key不存,则新增key和param(线程id+redisClientid)记录,value为数值型,value=1。 如果,key + param存在,则表名key已被默认线程持有,并且这个线程就是当前线程。如果,key + param不存在,则表明key已被默认线程持有,并且这个线程不是当前线程。
2. redisson释放锁的逻辑如何实现
因为加锁时,会设置过期时间。所以就算不主动解锁。key过期了就相当于解锁了。
redisson的解锁过程如下图。先判断线程是否持有该锁。如果有,则value值减1。然后判断value是否大于0,如果大于0,则给key设置一个默认的过期时间30秒;如果等于0,则可以删除key和发布一个key删除事件。
3. redisson释放锁时,如何唤起其它线程取争抢锁
很简单,使用发布订阅的机制。
释放锁时,发布锁释放消息。由于,争抢锁的线程在之前就订阅了这个消息。所以在接收到锁释放的消息后,就会立即再次尝试获取锁资源。
4. redisson如何解决A线程加锁,但是B线程去释放锁的问题
redi支持key+param的方式进行匹配。这里的param有点像标签。当线程A获取到redis的锁资源后,会将param设置为线程id+redisson连接管理器id。然后解锁的时候,也是需要带上parma匹配的。匹配不上是解锁不了的。 redis.call('hexists', KEYS[1], ARGV[2])
5. redisson如何实现公平锁?
这里使用了和ReentrantLock一样的思路,使用了等待队列。
大概的逻辑:
1. 如果当前不是加锁状态,并且等待队列是空。则当前线程可以直接获取锁。
2. 如果当前不是加锁状态,但是等待队列不为空。则当前线程需要加入等待队列中,排队获取锁。
3. 等待中的线程如何被唤醒。依然是通过消息通知的方式。在公平锁时,需要从队列头取出线程id,只有当前线程和取出的线程id匹配才能执行,否则继续休眠。
可以看看这段代码:org.redisson.RedissonFairLock#tryLockInnerAsync
6. redisson如何实现读写锁?
读锁加锁过程
读锁释放过程
写锁的获取过程
写锁的释放过程
7. redisson的锁如果是自己过期的,是怎么唤醒其他等待线程的?
redis是有key过期事件这样的特性的。当锁的过期时间到期,Redis 会触发一个事件通知,然后 Redisson 的订阅者会接收到这个通知。并唤醒订阅的线程。
总结
读锁获取过程
读锁释放过程
写锁获取场景
写锁释放
公平锁的实现方式