概述
1.本地锁的局限性
常见的本地锁有synchronized和lock锁,这些都是本地锁
2.本地锁带来的问题
1.1 单体应用,如果本地不加同步锁的时候,在高并发的情况下,会发生线程安全问题。
1.2 单体应用,如果加上本地锁的时候,在高并发情况下,可以解决安全问题,但是效率会变低。
但是,在集群的情况下,会怎么样呢?
问题: 在分布式部署的时候,无法保证只有只有一个进程在当前的程序内执行
解决: 需要一把公共锁(分布式锁)
流程: 此处使用redis
1.多个客户端同时获取锁(setnx) -> 加锁
2.获取成功,执行业务逻辑(从db中获取数据,放入缓存),执行完成释放锁(del) -> 释放锁
3.其他客户端等待重试 -> 自旋
但是会有新的问题: 程序执行过程中出现异常,导致锁无法释放,就会造成死锁。
解决: 给锁设置一个过期时间
//需要进行加锁
Boolean lock = stringredisTemplate.opsForValue().setIfAbsent("lock", "111");
//设置过期时间
stringredisTemplate.expire("lock", Duration.ofSeconds(3L));
新问题: 在获取锁后,设置过期时间之前出现问题
解决:在set时指定过期时间(推荐)
在Redis 2.6.12版本以前,SET命令总是返回ok。
在Redis 2.6.12版本开始,SET在设置操作成功完成时,才返回ok。
如果设置了NX或者XX,但是因为条件没达到而造成操作为执行,那么命令返回空批量回复(NULL Bulk Reply)
Boolean lock = stringredisTemplate.opsForValue().setIfAbsent("lock", "111", 3, TimeUnit.SECONDS);
新问题: 线程1在执行任务期间,因为一些问题,在还没到达释放锁之前,锁过期释放了。此时线程2看到锁被释放,拿锁进来完成任务,但是线程1还没执行完。此时,线程1执行到达释放锁的位置,会将线程2的锁给释放。会导致同一应用内,存在多个线程,出现线程安全问题。
原因: 锁的误删除
解决: 每个线程在加锁的时候,生成一个UUID,用于标识当前的锁。
Boolean lock = stringredisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
if (uuid.equals(stringredisTemplate.opsForValue().get("lock"))){
//释放锁
stringredisTemplate.delete("lock");
}
新问题: 在校验uuid之后,释放锁之前,锁到期释放了,此时线程2进入,线程1继续往下执行,还是会误删掉线程2的锁。(不原子)
解决:不使用delete释放锁,使用Lua脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
分布式锁的总结:
1.加锁
//生成锁的标识
String uuid = UUID.randomUUID().toString().replace("_","");
//设置锁
stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
2.释放锁
//定义Lua脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//定义对象 设置返回值执行
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
//设置脚本
defaultRedisScript.setScriptText(script);
//设置返回值类型
defaultRedisScript.setResultType(Long.class);
//执行
stringredisTemplate.execute(defaultRedisScript, Arrays.asList("lock"),uuid);
3.自旋重试
//其他线程等一下小会
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.testLock();
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件
- 互斥性。在任意时刻,只能有一个客户端持有锁
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁
- 解铃还需系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了
- 加锁和解锁必须具有原子性
最后
以上就是务实裙子为你收集整理的本地锁和分布式锁的全部内容,希望文章能够帮你解决本地锁和分布式锁所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复