什么是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

}

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

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

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

redis.call('hincrby', KEYS[1], ARGV[2], 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]);"
  • 删除锁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;

三、流程图

在这里插入图片描述

四、示例

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

运行结果:

com.koo.RedissonTest                     : 获取锁成功....1
com.koo.RedissonTest                     : 获取锁成功....2
com.koo.RedissonTest                     : 开始执行业务....2
com.koo.RedissonTest                     : 释放锁....2
com.koo.RedissonTest                     : 释放锁....1

猜你喜欢

转载自blog.csdn.net/lovoo/article/details/130910627