一般很少使用数据库锁,性能不好并且容易产生死锁。
基于MySQL锁表
该实现方式完全依靠数据库唯一索引来实现,当想要获得锁时,即向数据库中插入一条记录,释放锁时就删除这条记录。这种方式存在以下几个问题:
(1) 锁没有失效时间,解锁失败会导致死锁,其他线程无法再获取到锁,因为唯一索引insert都会返回失败。
(2) 只能是非阻塞锁,insert失败直接就报错了,无法进入队列进行重试
(3) 不可重入,同一线程在没有释放锁之前无法再获取到锁
采用乐观锁增加版本号
根据版本号来判断更新之前有没有其他线程更新过,如果被更新过,则获取锁失败。
可以基于 Redis 等缓存中间件来实现分布式锁,主要实现思路如下:
基于 setnx、expire 两个命令来实现
基于setnx(set if not exist)的特点,当缓存里 key 不存在时,才会去 set,否则直接返回 false。如果返回 true 则获取到锁,否则获取锁失败,为了防止死锁,我们再用 expire 命令对这个 key 设置一个超时时间来避免。
但是这里看似完美,实则有缺陷,当我们 setnx 成功后,线程发生异常中断,expire 还没来的及设置,那么就会产生死锁。
解决上述问题有两种方案:
**第一种是采用 redis2.6.12 版本以后的 set,**它提供了一系列选项:
**第二种采用 setnx(),get(),getset()实现,**大体的实现过程如下:
(1) 线程 Asetnx,值为超时的时间戳(t1),如果返回 true,获得锁。
(2) 线程 B 用 get 命令获取t1,与当前时间戳比较,判断是否超时,没超时 false,如果已超时执行步骤3。
(3) 计算新的超时时间 t2,使用 getset 命令返回 t3(这个值可能其他线程已经修改过),如果t1==t3,获得锁,如果 t1!=t3 说明锁被其他线程获取了。
(4) 获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)。