参考:https://www.cnblogs.com/wangyingshuo/p/14510524.html
介绍
多线程环境下控制对共享资源的访问以保证数据一致性。
特点:跨进程、跨服务、跨服务器
互斥性 ——任意时刻,只有一个客户持有锁
超时释放——持有锁超时,可以释放,防止死锁
可重入——一个线程获取锁后可以再次请求锁,加几次锁就需要释放几次锁
自动续期——守护线程判断是否需要续期
高可用,高性能——加锁和解锁需要开销尽可能低,同时也要保证高可用
安全性——锁只能被持有的线程删除,不能被其他线程删除
jvm本地锁有三种情况导致锁失效
多例模式、
事务 读未提交
集群部署
单机版:lua脚本
setnxex 不存在才会设值并设置过期时间 nx 排他独占 过期时间 防止死锁
问题1:业务超时锁过期自动删除 过期时间稍微设长一点 更好点需要自动续期
问题2:超时后其他线程进行了加锁,此时业务执行完进行锁删除 ,需要判断是否是自己的锁,以免误删别人的锁 删除锁需要加判断是否是自己的 UUID
=》判断锁和删除锁 需要是原子操作,因为可能还没来得及删除,redis中锁过期,被其他线程占用了锁,此时删除的依然是其他线程的锁
解决:redis官方提供了lua脚本来实现判断并删除锁的原子操作
=>可重入 hash + lua脚本
使用hash结构
设置锁:hsetnx lock uuid+线程id value
判断锁是否存在:hexists lock uuid+线程id
重入计数加一:hincrby lock uuid+线程id
hset key field value:hset lock uuid+线程id count (count为重入次数)
使用uuid+线程id标识自己的锁,每个服务生成一个uuid 唯一 , 保证重入时锁是同一个锁
加锁:
1.判断锁是否被独占(exists),如果没有,直接获取锁(hset/hincrby)并设置过期时间(expire)
2.如果锁被占用,则判断是否是当前线程占用的,如果是则重入(hincrby)并设置过期时间(expire)
3.否则获取锁失败,重试

=》自动续期(定时任务)
子线程处理自动续期
Timer定时器+lua脚本
判断锁是否自己的 hexists == 1 , 执行expire重置
解锁:
直接del会有误删问题
先判断再删除并保证原子操作:lua脚本
hash+lua脚本;可重入:
1.判断当前线程的锁是否存在,不存在返回nil,将来抛出异常
2.存在则直接减1(hincrby -1),判断减1后的值是否为0,为0则释放锁(del),并返回1
3.不为0,则返回0
重试:递归、循环

redis方式实现二:redisson
对于锁的自动续期问题提供更好的解决方案
定时守护线程,每个一段时间检查锁是否还存在,存在则对锁的过期时间进行延长,防止锁提前释放
主要是提供看门狗解决自动续期问题
属于单机版方案

线程加锁成功=》启动一个看门狗(守护线程),每隔10s检查一下
=》如果线程还持有锁,则延长 =》实现自动续期
redis实现方式三:Redlock+redisson
redis一般集群部署
如果线程1在master节点拿到锁,但是加锁的key还没有同步到slave节点,恰好这时master发生了故障,一个slave节点就会升级为master节点。
线程2可以获取同一个key的锁,线程1也以为拿到了锁,这样锁就失效了。
redis提供分布式锁算法:RedLock。
核心思想:
搞多个Redis master部署,以保证不会同时宕掉。并且master节点完全相互独立,相互之间不存在数据同步。同时需要确保在这多个master实例上是在与redis单实例使用相同的方法来获取和释放锁
