• 什么是Redission可重入锁,其实现原理是什么?


    一、概述

    Redission是一个可重入锁,它可以在分布式系统中用于实现互斥锁。这种锁可以允许多个线程同时获取锁,但在任何给定时间只有一个线程可以执行受保护的代码块。

    Redission锁提供了一种简单的方法来保证在分布式系统中的互斥性,同时支持可重入性。这意味着一个线程可以在获取锁之后再次获取同一个锁,而不需要等待锁释放。

    • Redission锁使用lua脚本来实现加锁和解锁操作。当一个线程想要获取锁时,它会发送一个lua脚本到redis服务器。这个脚本首先会检查当前时间是否比之前获取锁的时间早,如果不是,则脚本会返回一个错误。否则,它会将当前线程的id添加到一个列表中,表示该线程已经获取了锁。如果锁没有被其他线程持有,那么当前线程就可以执行受保护的代码块了。
       
    • 当一个线程完成执行受保护的代码块时,它需要发送一个lua脚本到redis服务器来解锁。这个脚本会检查当前线程是否持有锁,如果是,则它将从列表中删除当前线程的id并返回成功。否则,脚本会返回一个错误。

    Redission锁还支持超时设置,这意味着锁只能在一定的时间内有效。当锁超时后,其他线程就可以获取锁并执行受保护的代码块了。

    Redission是一个高性能的锁实现,它被广泛用于分布式系统中的互斥操作。它可以与多种语言和框架集成,包括Java、C++、Python和Ruby等。

    二、原理

    在Lock锁中,他是借助于底层的一个voaltile的一个state变量来记录重入的状态的,比如当前没有人持有这把锁,那么state=0,假如有人持有这把锁,那么state=1,如果持有这把锁的人再次持有这把锁,那么state就会+1 ,如果是对于synchronized而言,他在c语言代码中会有一个count,原理和state类似,也是重入一次就加一,释放一次就-1 ,直到减少成0 时,表示当前这把锁没有被人持有。

    在redission中,我们的也支持支持可重入锁

    在分布式锁中,他采用hash结构用来存储锁,其中大key表示表示这把锁是否存在,用小key表示当前这把锁被哪个线程持有,所以接下来我们一起分析一下当前的这个lua表达式

    这个地方一共有3个参数

    KEYS[1] : 锁名称

    ARGV[1]: 锁失效时间

    ARGV[2]: id + “:” + threadId; 锁的小key

    exists: 判断数据是否存在 name:是lock是否存在,如果==0,就表示当前这把锁不存在

    redis.call(‘hset’, KEYS[1], ARGV[2], 1);此时他就开始往redis里边去写数据 ,写成一个hash结构

    Lock{id + **":"** + threadId :  1
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果当前这把锁存在,则第一个条件不满足,再判断

    redis.call('hexists', KEYS[1], ARGV[2]) == 1
    
    • 1

    此时需要通过大key+小key判断当前这把锁是否是属于自己的,如果是自己的,则进行

    redis.call('hincrby', KEYS[1], ARGV[2], 1)
    
    • 1

    将当前这个锁的value进行+1 ,redis.call(‘pexpire’, KEYS[1], ARGV[1]); 然后再对其设置过期时间,如果以上两个条件都不满足,则表示当前这把锁抢锁失败,最后返回pttl,即为当前这把锁的失效时间

    如果小伙帮们看了前边的源码, 你会发现他会去判断当前这个方法的返回值是否为null,如果是null,则对应则前两个if对应的条件,退出抢锁逻辑,如果返回的不是null,即走了第三个分支,在源码处会进行while(true)的自旋抢锁。

    • 添加锁脚本
    "if (redis.call('exists', KEYS[1]) == 0) then " +
         "redis.call('hset', KEYS[1], ARGV[2], 1); " +
         "redis.call('pexpire', KEYS[1], ARGV[1]); " +
         "return nil; " +
     "end; " +
     "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
         "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
         "redis.call('pexpire', KEYS[1], ARGV[1]); " +
         "return nil; " +
     "end; " +
     "return redis.call('pttl', KEYS[1]);"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 删除锁Lua脚本
    if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
        return nil;
    end ;
    local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
    if (counter > 0) then
        redis.call('pexpire', KEYS[1], ARGV[2]);
        return 0;
    else
        redis.call('del', KEYS[1]);
        redis.call('publish', KEYS[2], ARGV[1]);
        return 1;
    end ;
    return nil;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、流程图

    在这里插入图片描述

    四、示例

    @Slf4j
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class RedissonTest {
    
        @Resource
        private RedissonClient redissonClient;
    
        private RLock rLock;
    
        @BeforeEach
        public void setUp() {
            rLock = redissonClient.getLock("order");
        }
    
    
        @Test
        public void method1() {
            boolean isLock = rLock.tryLock();
            if (isLock) {
                log.error("获取锁失败.....1");
                return;
            }
            try {
                log.info("获取锁成功....1");
                method2();
                log.info("开始执行业务....1");
            } finally {
                log.info("释放锁....1");
                rLock.unlock();
            }
        }
    
        public void method2() {
            boolean isLock = rLock.tryLock();
            if (isLock) {
                log.error("获取锁失败.....2");
                return;
            }
            try {
                log.info("获取锁成功....2");
                log.info("开始执行业务....2");
            } finally {
                log.info("释放锁....2");
                rLock.unlock();
            }
        }   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    运行结果:

    com.koo.RedissonTest                     : 获取锁成功....1
    com.koo.RedissonTest                     : 获取锁成功....2
    com.koo.RedissonTest                     : 开始执行业务....2
    com.koo.RedissonTest                     : 释放锁....2
    com.koo.RedissonTest                     : 释放锁....1
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    PDO 与 MySQLi:PHP 数据库 API 之战
    当Java遇到Redis:Jedis实战入门
    kubernetes RBAC
    环境影响评价知识点整理
    Leetcode2-两数相加代码详解
    Rookit系列二【文件隐藏】【支持Win7 x32/x64 ~ Win10 x32/x64平台的NTFS文件系统】
    国产开发板上打造开源ThingsBoard工业网关--基于米尔芯驰MYD-JD9X开发板
    管理员必须知道的RADIUS认证服务器的部署成本
    基于Echarts实现可视化数据大屏北斗车联网大数据服务平台首页面
    uni-app 微信小程序中如何通过 canvas 画布实现电子签名?
  • 原文地址:https://blog.csdn.net/lovoo/article/details/130910627