我是靠谱客的博主 务实裙子,最近开发中收集的这篇文章主要介绍本地锁和分布式锁,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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();

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

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

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

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

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

最后

以上就是务实裙子为你收集整理的本地锁和分布式锁的全部内容,希望文章能够帮你解决本地锁和分布式锁所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部