一、实现思路
实现分布式锁时需要实现的两个基本方法:
-
获取锁:
- 互斥:确保只能有一个线程获取锁,使用redis中的setnx操作
- 非阻塞:尝试一次,成功返回true,失败返回false
-
释放锁:
- 手动释放
- 超时释放:获取锁时添加一个超时时间
但是这里还存在一个问题:我们加过期时间的目的就是为了避免因为服务宕机导致锁无法释放。但是如果我执行 setnx
成功了,但还没来得及执行 expire
就已经宕机了,那是不是依然没有办法释放?所以我们必须保证setnx这个获取锁的动作、和添加锁过期时间,这两者要么同时成功,要么同时失败,因此需要具有原子性,那这业务该怎么做呢?
其实redis的set命令后面可以跟上很多参数,通过EX就可以指定过期时间,以秒为单位,PS是以毫秒为单位。
除此之外可以减,它后面还可以跟上NX,这个NX效果就跟setnx一样,只有当key不存在的时候,才能去set
所以说我们可以在一个set命令中通过设置参数,同时实现nx和过期时间的效果,这样就可以实现原子性的操作

并且我们给它也设置了有效期,过10s可以发现已经过期了

因此最终的方案如下图

二、思考
其实到这已经可以实现分布式锁了,但是还有一个小问题。
在获取锁的时候,要么成功,要么失败。成功了返回OK,失败返回 nil
,但是失败了以后我们又该怎么做呢?
其实在我们JDK中提供的锁有两种机制,一种是:如果获取失败,就会阻塞等待,等到有人释放锁为止,也就是说是一种阻塞式的获取。
还有一种方式就是非阻塞式的获取,也就是说我来尝试获取锁,如果我获取失败了,我就会立即结束,返回一个结果,而不是一直尝试一直等待。
在这我们会实现非阻塞机制,因为阻塞式第一、对CPU是有一定浪费的,第二、这种阻塞式实现起来相对会比较麻烦一些。
因此我们会利用非阻塞的方式,也就是说我尝试一次,成功返回true,失败返回false,然后我就不再尝试了。
三、整体流程
我们利用redis 的setNx 方法,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了nil;如果结果是OK,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑
