redisson中Semaphore的信号量介绍及其原理

news2024/10/7 20:25:52

目录

1 基本介绍

1.1API介绍

1.2 示例

 2 源码解析

2.1  Semaphore设置许可数量(trySetPermits(int permits))

2.2 尝试获取许可(boolean tryAcquire())

3 Lua脚本

        3.1 加锁lua脚本

         3.2 解锁lua脚本 


1 基本介绍

Semaphore通常叫信号量,可以用来同时控制访问特定资源的线程数量,通过协调各个线程,保证合理的使用资源。

1.1API介绍

public interface RSemaphore extends RExpirable, RSemaphoreAsync {

    // 获得一个permit
    void acquire() throws InterruptedException; 

    //获得var1个permit
    void acquire(int var1) throws InterruptedException; 
 
    //尝试获得permit
    boolean tryAcquire(); 

   //尝试获得var1个permit
    boolean tryAcquire(int var1); 

   //尝试获得permit, 等待时间var1
    boolean tryAcquire(long var1, TimeUnit var3) throws InterruptedException;

   //尝试获得var1个permit, 等待时间var2
    boolean tryAcquire(int var1, long var2, TimeUnit var4) throws InterruptedException;

   //释放1个permit
    void release();

   //释放var1个permit
    void release(int var1);
 
   //信号量的permits数
    int availablePermits();

   //清空permits
    int drainPermits();

  //设置permits数
    boolean trySetPermits(int var1);

//添加permits数
    void addPermits(int var1);
}

1.2 示例

@RestController
@RequestMapping("/rsemaphone")
public class TestRsemaphoreController {

    @Resource
    private RedissonClient redissonClient;

    private ExecutorService executorService= Executors.newFixedThreadPool(5);

    /**
     * redission信号量
     */
    @RequestMapping("/rseTrySetPermits")
    public void rseTrySetPermits() throws InterruptedException {
        RSemaphore semaphore = redissonClient.getSemaphore("123");
        semaphore.trySetPermits(5);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                try {
                    semaphore.acquire();
                    System.out.println("线程:"+Thread.currentThread().getName()+"获得permit");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("线程:"+Thread.currentThread().getName()+"释放permit");
                    semaphore.release();

                }
            });
        }

    }

}

运行结果:

 2 源码解析

2.1  Semaphore设置许可数量(trySetPermits(int permits))

    public boolean trySetPermits(int permits) {
        return (Boolean)this.get(this.trySetPermitsAsync(permits));
    }

    public RFuture<Boolean> trySetPermitsAsync(int permits) {
        return this.commandExecutor.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
//判断分布式信号量是否存在,如果不存在才设置
"local value = redis.call('get', KEYS[1]); if (value == false or value == 0) 
//使用string数据结构设置信号量许可数
then redis.call('set', KEYS[1], ARGV[1]); 
//发布一条消息到redisson_sc:{semaphore}通道
redis.call('publish', KEYS[2], ARGV[1]); 
//设置成功返回1
return 1;end;
//否则返回0
return 0;", Arrays.asList(this.getRawName(), this.getChannelName()), new Object[]{permits});
    }

 可以发现只有设置许可其实就是利用lua将值设置到redis中

RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
semaphore.trySetPermits(5);

2.2 尝试获取许可(boolean tryAcquire())

尝试获取许可:可以看到获取许可的底层还是通过lua来实现的,如果能够成功获取返回true,否则返回false。

    public boolean tryAcquire(int permits) {
        return (Boolean)this.get(this.tryAcquireAsync(permits));
    }

    public RFuture<Boolean> tryAcquireAsync() {
        return this.tryAcquireAsync(1);
    }

    public RFuture<Boolean> tryAcquireAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits amount can't be negative");
        } else {
            return permits == 0 ? RedissonPromise.newSucceededFuture(true) : this.commandExecutor.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
//获取当前剩余的许可数量
"local value = redis.call('get', KEYS[1]); 
//许可数量不为空 并且当前许可数量大于等于剩余的许可数量
if (value ~= false and tonumber(value) >= tonumber(ARGV[1])) 
//通过decrby减少剩余可用许可
then local val = redis.call('decrby', KEYS[1], ARGV[1]);
//返回1
 return 1; end; 
//其他情况返回0
return 0;", Collections.singletonList(this.getRawName()), new Object[]{permits});
        }
    }

从源码中可以看出获取许可就是通过操作redis中的数据,首先获取到剩余的许可数量,当只有剩余的许可数量大于想要获取的许可数量时返回1否则返回0.

3 Lua脚本

        3.1 加锁lua脚本

参数示例值含义
KEY个数1KEY个数
KEYS[1]my_first_lock_name        锁名
ARGV[1]60000持有锁的有效时间:毫秒
ARGV[2]58c62432-bb74-4d14-8a00-9908cc8b828f:1唯一标识:获取锁时set的唯一值,实现上为redisson客户端ID(UUID)+线程ID
  • 脚本内容

-- 若锁不存在:则新增锁,并设置锁重入计数为1、设置锁过期时间
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;
 
-- 若锁存在,且唯一标识也匹配:则表明当前加锁请求为锁重入请求,故锁重入计数+1,并再次设置锁过期时间
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]);

  • 脚本解读

问:返回nil、返回剩余过期时间有什么目的?

答:当且仅当返回nil,才表示加锁成功;客户端需要感知加锁是否成功的结果

         3.2 解锁lua脚本 

  • 脚本入参
参数示例值含义
KEY个数2KEY个数
KEYS[1]my_first_lock_name锁名
KEYS[2]redisson_lock__channel:{my_first_lock_name}解锁消息PubSub频道
ARGV[1]0redisson定义0表示解锁消息
ARGV[2]300000设置锁的过期时间;默认值30秒
ARGV[3]58c62432-bb74-4d14-8a00-9908cc8b828f:1唯一标识;同加锁流程
  • 脚本内容

-- 若锁不存在:则直接广播解锁消息,并返回1
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1; 
end;
 
-- 若锁存在,但唯一标识不匹配:则表明锁被其他线程占用,当前线程不允许解锁其他线程持有的锁
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    return nil;
end; 
 
-- 若锁存在,且唯一标识匹配:则先将锁重入计数减1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) then 
    -- 锁重入计数减1后还大于0:表明当前线程持有的锁还有重入,不能进行锁删除操作,但可以友好地帮忙设置下过期时期
    redis.call('pexpire', KEYS[1], ARGV[2]); 
    return 0; 
else 
    -- 锁重入计数已为0:间接表明锁已释放了。直接删除掉锁,并广播解锁消息,去唤醒那些争抢过锁但还处于阻塞中的线程
    redis.call('del', KEYS[1]); 
    redis.call('publish', KEYS[2], ARGV[1]); 
    return 1;
end;
 
return nil;

  • 脚本解读

 问:广播解锁消息有什么用? 

答:是为了通知其他争抢锁阻塞住的线程,从阻塞中解除,并再次去争抢锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/724856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

科技资讯|苹果Vision Pro新专利曝光,与消除晕动症的技术有关

欧洲专利局发布了一份苹果专利&#xff0c;与消除 Apple Vision Pro 晕动病背后的一些工作有关。苹果通过推出新的 R1 处理器实现了这一目标&#xff0c;苹果专利提供了处理器背后的一些详细技术信息&#xff0c;在第 86 号专利点指出&#xff1a;" 在某些实施方案中&…

驾驭计算机视觉的翅膀:论文找代码的几种必杀技!

摘要 对于CVer来说&#xff0c;「代码和找代码」能力都是一种很重要的能力&#xff0c;毕竟idea再好只有通过代码实现出来才能发文章和刷榜。当我们阅读一篇高质量或者英文论文时&#xff0c;如何去找到该文章实现的代码&#xff0c;进而结合文章内容和代码实现去更好的理解作…

PoseiSwap 更新第二期空投,持有 Zepoch 节点数量将决定空投回报

Nautilus Chain 是行业内首个模块化 Layer3 架构链&#xff0c;开发者能够基于模块化进行定制化开发&#xff0c;并有望进一步推动 Web3 应用向隐私、合规等方向发展。当然&#xff0c;Nautilus Chain 的特殊之处还在于为生态用户带来丰厚的空投预期&#xff0c;据悉上线 Nauti…

基于matlab使用标记增强技术将虚拟内容呈现到现实场景中(附源码)

一、前言 此示例演示如何使用基于标记的增强现实将虚拟内容呈现到场景中。 增强现实 &#xff08;AR&#xff09; 通过自然混合真实和虚拟内容来增强现实世界的场景&#xff0c;从而创建新颖的应用程序。例如&#xff0c;增强现实应用程序可以添加虚拟标尺&#xff0c;使用户…

如何正确使用 ThreadLocal

1 前言 当多线程访问共享且可变的数据时&#xff0c;涉及到线程间同步的问题&#xff0c;并不是所有时候&#xff0c;都要用到共享数据&#xff0c;所以就需要ThreadLocal出场了。 ThreadLocal又称线程本地变量&#xff0c;使用其能够将数据封闭在各自的线程中&#xff0c;每…

数据管理成熟度评估DCMM之生产企业数据战略管理办法

生产企业数据战略管理办法 第一部分&#xff1a;导言 随着信息技术的快速发展和数据规模的急剧增长&#xff0c;生产企业越来越重视数据的价值和管理。有效的数据战略管理办法可以帮助生产企业更好地管理和利用数据资源&#xff0c;提高运营效率、决策质量和创新能力。本文将…

SpringMVC数据传递总结

文章目录 1. 分析总结2. 普通格式数据2.1 普通参数2.2 pojo参数2.3 嵌套pojo参数2.4 数组 -- 普通参数2.5 集合 -- 普通参数2.6 web容器添加过滤器指定字符集 3. JSON格式数据3.1 相关准备3.2 json数组(基本)3.3 json对象(pojo)3.4 json数组(pojo) 1. 分析总结 1.1 普通格式数据…

K8S平台安全框架

平台安全框架 1 平台安全框架1.1 安全框架1.1.1 认证框架1.1.2 框架解读 1.2 认证实践1.2.1 令牌用户1.2.2 证书用户 1.3 授权实践1.3.1 集群用户1.3.2 角色基础1.3.3 授权基础1.3.4 用户组实践1.3.5 SA授权1.3.6 SA秘钥 1.4 准入实践1.4.1 准入基础1.4.2 优先调度1.4.3 资源配…

F2-NeRF阅读日志

看到了一篇很好的paper&#xff0c;记录一下&#xff0c;参考&#xff1a; https://www.bilibili.com/video/BV1Lz4y187jL/?spm_id_from333.337.search-card.all.click&vd_sourcea059a118f33728f79abd79e02f8f72d4 https://zhuanlan.zhihu.com/p/618362291 latex写的&am…

Qt5编译使用QFtp模块(环境:win+Qt5.15.2+msvc2019)

目录 QFtp下载编译配置QFtp模块测试 QFtp下载 下载方式较多&#xff0c;可以从github上进行下载&#xff1a;https://github.com/qt/qtftp.git 。 我已将下载好的ftp源码资源放出来了&#xff0c;可以直接下载0积分&#xff1a;链接跳转。 编译 使用Qt Create打开工程后&…

DuDuTalk:4G录音工牌在汽车试乘试驾场景中有什么独特应用价值?

在市场竞争越来越激烈的今天&#xff0c;不管是新能源市场还是燃油车市场&#xff0c;试乘试驾已经当仁不让地成为了几乎所有汽车品牌关注的焦点。特斯拉、“蔚小理”、奔驰、宝马等头部品牌&#xff0c;对于试乘试驾的重视度一定程度上甚至已经超过了展厅接待。 然而&#xf…

解决notion共享网址无法复制的问题

1、打开url Notion – The all-in-one workspace for your notes, tasks, wikis, and databases. 2、选中要复制的内容。 3、右击鼠标&#xff0c;选择“打印” 4、在打印界面中选中要复制的内容&#xff0c;然后按“复制” 复制完成。

Stable Difussion能做什么?

​扩散模型&#xff08;Diffusion Model&#xff09;​ 稳定扩散模型&#xff08;Stable Diffusion&#xff09;属于深度学习模型中的一个大类&#xff0c;即扩散模型。它们属于生成式模型&#xff0c;这意味着它们是被设计用于根据学习内容来生成相似的新的数据的。对于稳定扩…

Vue2与Vue3相应原理区别

Vue3.0中的响应式原理 vue2.x的响应式 1.实现原理&#xff1a; 对象类型&#xff1a;通过Object.defineProperty()对属性的读取、修改进行拦截&#xff08;数据劫持&#xff09;。数组类型&#xff1a;通过重写更新数组的一系列方法来实现拦截。&#xff08;对数组的变更方法…

测试老鸟整理,性能测试高并发压力测试-案例,进阶之道...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 单个接口的压测&a…

通过foxmail同步其他邮箱邮件到我司邮箱

1、先通过foxmail 登录需要备份邮件的邮箱帐号&#xff0c;全选需要备份的邮件&#xff0c;右键选择“导出邮件”。 2、在foxmail中使用IMAP协议登录我司邮箱帐号&#xff0c;右键选择收件箱或其他文件夹导入邮件&#xff0c;将之前导出备份的邮件文件全选导入。 3、导入完成后…

glibc缺陷居然会导致MySQL卡住?

问题来源&#xff1a; 版本&#xff1a;5.7.25。 现象&#xff1a;备机主从延迟不断变大&#xff0c;无法登陆数据库&#xff0c;建立连接时卡住&#xff0c;但很快恢复正常了。 分析&#xff1a; 常规分析&#xff1a; 通常情况下&#xff0c;这类问题无法分析&#xff0c…

vmware17安装openkylin

官网 系统下载-openKylin 开放麒麟社区官网 | 开源聚力&#xff0c;共创未来 下载链接 https://www.openkylin.top/downloads/download-smp.php?id18 安装 点击浏览&#xff0c;选择镜像 修改服务器cpu配置 修改内存配置 修改网络连接方式 点击启动 等待安装完成 出现上图说…

比较两个Excel表格中的数据,不相同的高亮显示

下面是常用的在Excel中比较两个Excel表格数据的方法&#xff0c; 比如要比较下面A和B中的数据是否一致&#xff1a; 可以这样做&#xff1a;

ikbc键盘2.4G接收器丢失,重新对码

我的键盘&#xff1a;ikbc W200 1.键盘关掉重开&#xff1b; 2.新接收器插在电脑上&#xff1b; 3.电脑上打开软件&#xff0c;点开始对码&#xff0c;一会就连接上了。 对码软件放在这里&#xff1a; 我用夸克网盘分享了「IKBC 对码.rar」&#xff0c;点击链接即可保存。打开…