我是靠谱客的博主 务实裙子,这篇文章主要介绍本地锁和分布式锁,现在分享给大家,希望可以做个参考。

1.本地锁的局限性

常见的本地锁有synchronized和lock锁,这些都是本地锁

2.本地锁带来的问题

1.1 单体应用,如果本地不加同步锁的时候,在高并发的情况下,会发生线程安全问题。

1.2 单体应用,如果加上本地锁的时候,在高并发情况下,可以解决安全问题,但是效率会变低。

但是,在集群的情况下,会怎么样呢?

问题: 在分布式部署的时候,无法保证只有只有一个进程在当前的程序内执行

        解决:   需要一把公共锁(分布式锁)

流程: 此处使用redis

                1.多个客户端同时获取锁(setnx)   ->   加锁

                2.获取成功,执行业务逻辑(从db中获取数据,放入缓存),执行完成释放锁(del) -> 释放锁

3.其他客户端等待重试 -> 自旋

                   但是会有新的问题: 程序执行过程中出现异常,导致锁无法释放,就会造成死锁。

                  解决: 给锁设置一个过期时间

复制代码
1
2
3
4
5
//需要进行加锁 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)

复制代码
1
2
Boolean lock = stringredisTemplate.opsForValue().setIfAbsent("lock", "111", 3, TimeUnit.SECONDS);

新问题: 线程1在执行任务期间,因为一些问题,在还没到达释放锁之前,锁过期释放了。此时线程2看到锁被释放,拿锁进来完成任务,但是线程1还没执行完。此时,线程1执行到达释放锁的位置,会将线程2的锁给释放。会导致同一应用内,存在多个线程,出现线程安全问题。

原因: 锁的误删除

解决: 每个线程在加锁的时候,生成一个UUID,用于标识当前的锁。

复制代码
1
2
3
4
5
6
7
8
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.加锁 

复制代码
1
2
3
4
5
6
//生成锁的标识 String uuid = UUID.randomUUID().toString().replace("_",""); //设置锁 stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);

        2.释放锁

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
//定义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.自旋重试

复制代码
1
2
3
4
5
6
7
//其他线程等一下小会 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } this.testLock();

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件

- 互斥性。在任意时刻,只能有一个客户端持有锁

- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁

- 解铃还需系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了

- 加锁和解锁必须具有原子性

最后

以上就是务实裙子最近收集整理的关于本地锁和分布式锁的全部内容,更多相关本地锁和分布式锁内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(75)

评论列表共有 0 条评论

立即
投稿
返回
顶部