需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景:集群情况下的定时任务、抢单、幂等性场景。
下面先来看一个抢卷场景:
以下情况会出现超卖情况:
因为线程会交替执行,所以线程查询优惠价的数据量后,线程2又查询优惠券的数据量,此时如果优惠券总数是1,最后优惠券的数量会变成-1,出现超卖。
使用synchronized解决:
但在集群的情况下,这种方式(本地加锁)会失效,因为synchronized是本地锁,这个锁属于JVM,每个服务都有各自的JVM,它只能解决同一个JVM下线程的互斥:
此时需要加分布式锁:
redis分布式锁
基于redis来实现分布式锁,底层使用的是setnx和lua脚本。
图1-1
上图中,必须设置锁的超时时间,否则当服务宕机的时候就不会释放锁了。
redisson实现的分布式锁如何合理控制锁的有效时长
继续看图1-1,因为加锁的时候给了一个失效时间,假如业务的执行时机太长,已经超出了锁的释放时间,但此时业务还没有执行完成,假如其他线程来获得锁就会获得成功。
出现这种情况后,可以给锁续期:redisson实现的分布式锁-执行流程:
- 有一个线程(线程1)过来,它获取锁并加锁成功后,然后就会去操作redis
- 但加锁成功后,会另开一个线程(Watch dog)监控 持有锁的线程,给持有锁的线程 增加锁的持有时间(续期)。续期的规则为每隔 releaseTime/3(releaseTime是锁的过期时间) 的时间做一次续期,默认为10秒。
- 手动释放锁,手动释放锁之后,需要通知对应线程的Watch dog不需要再监听了
图2-1
假如,现在又来了一个新的线程(线程2),想获取锁,它先尝试去加锁,如果加锁成功,和 图2-1的流程是一样的,但如果没有加锁成功呢?在redisson实现的分布式锁中,它设置了一个while循环来不断的尝试去加锁,如果加锁成功了就成功了,如果线程1一直没有释放锁,线程2有一个阈值,只要循环到了阈值,线程2就会获取锁失败,这种机制会在高并发下,大程度增加分布式锁的性能:
下图为redisson的使用,所有的redisson命令基于lua脚本完成,保证脚本执行的原子性
redisson实现的分布式锁是否可以重入
首先分析下图的代码:
- 在add1中尝试获取了锁,再去调用add2,add又去尝试获取锁,此时都是同一个线程,可以重入。
回答:可以重入,判断是否是当前线程,通过线程id去判断,如果是同一个线程就可重入,否则不能
redisson中可重入的实现
使用hash类型来存储锁数据(线程的唯一标识和重入次数):
- 每加一次锁,重入次数都会+1,每释放一次锁重入次数-1,全部释放完变成0后删除锁信息。
redisson实现的分布式锁可以保证主从一致吗
不能解决,但可以使用红锁保证主从一致,但性能太低,一般项目中用的不多,哪有服务器天天挂;如果想要保证数据的强一致性就去使用CP思想的zookeeper。