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

目录

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 {// 获得一个permitvoid acquire() throws InterruptedException; //获得var1个permitvoid acquire(int var1) throws InterruptedException; //尝试获得permitboolean tryAcquire(); //尝试获得var1个permitboolean tryAcquire(int var1); //尝试获得permit, 等待时间var1boolean tryAcquire(long var1, TimeUnit var3) throws InterruptedException;//尝试获得var1个permit, 等待时间var2boolean tryAcquire(int var1, long var2, TimeUnit var4) throws InterruptedException;//释放1个permitvoid release();//释放var1个permitvoid release(int var1);//信号量的permits数int availablePermits();//清空permitsint drainPermits();//设置permits数boolean trySetPermits(int var1);//添加permits数void addPermits(int var1);
}

1.2 示例

@RestController
@RequestMapping("/rsemaphone")
public class TestRsemaphoreController {@Resourceprivate 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]);
//返回1return 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.hqwc.cn/news/16101.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos7.6安装mysql

卸载mariadb 解决安装mysql与mariadb冲突问题&#xff08;卸载干净mariadb&#xff09;_何妨徐行的博客-CSDN博客 安装rpm包前可能需要的命令&#xff1a; yum install openssl-devel用于管理rpm包的工具 yum install lrzsz -y 文件传输缺乏rz 下载安装包 去mysql官网 把…

STM32使用printf重定向到USART(串口)并打印数据到串口助手

STM32使用sprintf打印数据到串口助手 一.背景知识二. 重定向printf到USART1三.使用printf打印hello,world到串口助手3.1 usart.c3.2 usart.h3.3 main.c 四. 实验现象五.结语 一.背景知识 我们知道我们在进行编程的时候&#xff0c;遇到问题&#xff0c;经常通过打印信息进行调…

拥抱“Zero ETL”未来,亚马逊云科技助力乐城堡加速数据分析

获得全球三千五百多万用户的认可的移动游戏企业乐城堡希望通过数据分析为游戏业务提供更好的决策支撑。乐城堡在亚马逊云科技上利用Amazon Redshift等服务构建属于自己的云上游戏数据分析平台&#xff0c;实现复杂查询&#xff0c;保证游戏运营人员能快速、近实时地获取所需的数…

SpringBoot配置外部Tomcat项目以及启动流程源码分析

1.SpringBoot配置外部Tomcat并打war包 2.SpringBoot配置外部Tomcat项目启动流程源码分析

快速创建ES集群

win10 中docker 设置 快速创建集群 访问 官网 elasticsearch/docs/reference/setup/install/docker at main elastic/elasticsearch GitHub 负责上面2个文件&#xff0c;并修改&#xff0c;修改如下 .env文件 # Password for the elastic user (at least 6 characters) …

【MES中的APS生产排程】

我记得APS生产排程里的销售订单,是通过CRM接口过来的。 但是今天了解到,销售订单是营销直接在MES里下单。 第一步:销售订单维护 提交,审核之后,就会出现在MPS运算界面中。 关闭后,MPS里的运算结果也会同步关闭。 第二步:MPS运算 可以看到,刚才新增的销售订单:RXSD…

Python-opcua 编程(3)历史数据读写

历史数据就是将opcua 信息模型中的某一些变量保存起来&#xff0c;以便Client 端程序能够读取历史数据&#xff0c;作各种数据处理。 Opcua 标准指出历史数据的读写&#xff0c;主要包括&#xff1a; 属性 Historizing 当设置为True 时&#xff0c;该变量支持历史数据读写 …

优化成本,探索WhatsApp API发送更经济的OTP验证信息

在现代的数字化世界中&#xff0c;安全性和使用者验证变得至关重要。随着移动应用程序和在线服务的普及&#xff0c;一次性密码&#xff08;OTP&#xff09;验证已经成为确保使用者身份验证的主要手段之一。然而&#xff0c;对于许多企业来说&#xff0c;发送OTP验证信息可能会…

多元回归预测 | Matlab基于高斯过程回归(GPR)的数据回归预测,matlab代码,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于高斯过程回归(GPR)的数据回归预测,matlab代码,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码

计算机体系结构基础知识介绍之缓存性能的十大进阶优化之关键词优先和提前重启以减少失误处罚、合并写入缓冲区以减少惩罚(五)

优化五&#xff1a;关键词优先&#xff0c;提前重启&#xff0c;减少漏判 处理器通常一次只需要缓存块中的一个字&#xff08;word&#xff09;。不要等待整个块被加载&#xff0c;而是在请求的字到达后就立即发送给处理器&#xff0c;并让处理器继续执行&#xff0c;同时填充…

加速大模型落地!使用4-bit训练Transformer,比FP16快2.2倍,提速35.1%

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2306.11987.pdf 项目地址&#xff1a;https://github.com/xijiu9/Train_Transformers…

裸机搭建k8s报错记录

安装教程参考 修复一、 cd /etc/kubernetes/manifests vim kube-scheduler.yaml注释掉 重启 systemctl restart kubelet.service问题二、 https://github.com/kubernetes/kubernetes/issues/70202 一直处于创建中状态 网络原因 cat << EOF > /run/flannel/subnet.…