2 Redis分布式锁
2.1 分布式锁
Sychonized只能保证单个jvm内部多个线程的互斥
多个jvm需要分布式锁:每个jvm都有内部的锁监视器,要用jvm外部的锁监视器,让每个进程都能看见锁监视器
线程1占用锁,在锁监视器监视下执行业务逻辑,其他线程获取锁失败重试,等待锁释放
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁
实现分布式锁的核心就是实现多进程之间的互斥,以下是三种实现分布式锁的方式
2.2 初步实现基于redis的分布式锁
如果获取锁之后,服务宕机了,会造成死锁,所以就需要给值设置一个过期时间 expire key seconds
ttl key 查看过期时间
keys * 查看所有键的信息
如果获取锁之后,设置过期时间之前宕机,还是死锁,所以要保证获取锁和设置过期时间的原子性:使用命令set key value ex seconds nx / SETEX key seconds value
jdk中提供了两种锁机制:
- 获取锁失败之后阻塞等待
- 如果获取失败会立即结束并返回一个结果,而不是继续等待
代码实现:
自动拆箱,封箱带来的空指针问题
Boolean到boolean会有自动拆箱的过程,如果success为null,会导致空指针问题,所以为了避免空指针,使用Boolean.True.equals(success),true返回true,false和null都返回false
2.3 分布式锁误删锁问题
问题描述:线程1未执行完业务,但是阻塞了,阻塞期间redis超时释放锁,这时线程2获取锁执行业务,线程2执行业务期间,线程1业务执行完成之后,线程1不知道自己锁释放之后被别人获取了,线程1执行释放锁操作,结果把线程2的锁释放了,线程2还在执行,线程3又来获取锁执行业务,导致线程安全问题
解决方案:释放锁之前,判断锁标识,看是不是自己的锁------------加线程id和uuid判断
2.4 解决误删问题
代码修改
2.5 分布式锁原子性问题
问题描述:判断锁标识和删除锁是两个操作,如果在线程1判断完锁标识之后,发现是自己的锁,但是在释放锁的时候阻塞了,此时的阻塞可能是由垃圾回收导致的fullGC会导致所有的线程阻塞。阻塞期间,线程1的redis锁过期释放,线程2在这时获取锁执行业务,在线程2执行业务的时候,线程1恢复并释放锁,这时候线程3再获取锁执行业务逻辑,造成线程安全问题
redis是有事务的,但是redis的事务能保证原子性,但是不能保证一致性
redis的事务中的多个操作是批处理操作,最终一次执行,先去查询,再判断,再释放,如果基于redis事务只能使用乐观锁机制确保释放时没有被修改
2.6 Lua脚本解决原子性问题
执行脚本
---------
代码改进
新建文件:
2.7 进一步优化
除了上面解决的问题,还存在下面的问题