重入:
获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁
不可重试:
只能尝试一次
超时释放:
在加锁时增加了过期时间,可以防止死锁,有安全隐患
主从一致:
向集群写数据时,主机需要异步地将数据同步给从机
Redission
在Redis基础上实现的Java驻内存数据网络,提供一系列分布式Java常用对象和分布式服务
包括分布式锁的实现
RLock lock = redissonClient.getLock("anyLock");
锁的名称是anyLock,是全局唯一标识,当多个客户端或线程尝试获取相同名称的锁时,就会发生竞争关系
Rlock代表是可重入锁,同一线程能够多次获取同一把锁,不会出现死锁
可重入锁原理
使用一个state变量记录重入状态,当没有人持有这把锁,state=0,假如有人持有,state=1,当前持有这把锁的人再次持有这把锁时,state+1
也就是重入一次加一,释放一次减一,直到减少为0代表这把锁没有被持有
在分布式锁中,使用hash存储锁
加锁代码:
这里共有三个参数:
KEYS[1]:锁名称
ARGV[1]:线程唯一标识
ARGV[2]:锁自动释放时间
首先判断锁是否存在
不存在就直接获取锁
存在就判断是不是自己的
是自己的就重入次数加一
不是自己的则获取锁失败
释放锁代码:
先判断锁是不是自己的
不是自己的直接返回
是自己的重入次数减一
如果重入次数为0则直接释放锁
不为0说明还不能释放,重制有效期
MutiLock原理
在搭建redis集群和主从的过程中
当执行写命令,写在主机上时,主机会同步数据给从机,但是假如主机在还没来得及把数据写入从机的时候就宕机,哨兵会发现主机宕机,并选举一个slave成为Master,新的Master实际并没有锁信息,此时锁信息就丢失掉了。
为解决这个问题,提出了MutiLock,使用这把锁就不使用主从,每个节点有相同地位
这把锁的逻辑是需要写入每一个主从节点上,只有所有服务器都成功写入才是加锁成功,假设有一个节点挂了,在获取锁时只要有一个节点拿不到就是加锁失败
加锁原理
设置多个锁时,会把多个锁添加到一个集合,用while循环去不停尝试拿锁,但存在一个总加锁时间,这个时间是需要加锁的个数*1500ms,假设在时间内所有锁加锁成功才算成功,只要有线程加锁失败,就会再次重试
总结
- 可重入锁是如何防止死锁的?
可重入锁允许同一线程多次获取同一把锁,通过计数器(state)记录重入次数。重入时递增,释放时递减,减至 0 才完全释放锁。这种机制避免了同一线程因重复请求同一锁而导致的阻塞,从而防止死锁。 - Redisson 的分布式可重入锁如何实现原子性?
Redisson 使用 Lua 脚本实现分布式锁的原子性操作。加锁时,通过 Lua 脚本原子性地检查锁是否存在、是否自己持有以及递增重入次数;释放锁时,同样通过 Lua 脚本原子性地递减重入次数并判断是否释放锁,确保操作的原子性。 - 超时释放机制的安全隐患是什么?如何应对?
超时释放可能导致锁提前释放,引发多个线程同时持有锁的问题。应对方法:
合理设置超时时间,根据业务执行时间调整
使用 watchdog 机制自动续期
业务代码中做好幂等性处理 - Redisson 的 MultiLock 如何解决主从架构中的锁丢失问题?
MultiLock 要求在多个独立节点上同时获取锁,只有全部获取成功才算加锁成功。即使主节点宕机,由于其他节点仍持有锁,新选举的主节点不会影响锁的有效性,从而避免锁丢失。 - MultiLock 加锁失败后的重试机制是怎样的?
MultiLock 通过 while 循环不断尝试加锁,总加锁时间为锁数量 * 1500ms。若在该时间内所有锁均加锁成功,则加锁成功;否则失败,释放已获取的锁并重新尝试。这种机制平衡了性能和可靠性。