在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。
锁相关概念
分类
new ReentrantLock(true);where(true) 再次试探获取,不阻塞线程
相关阅读:
使用 ThreadLocal 保存锁对应的唯一标识
加锁:使用 STRING 保存锁定标识, 'SET key value PX NX' 确保一个 key 只能加锁一次
解锁:Lua 脚本判断是自己加的锁进行释放
工具类
// 使用 ThreadLocal 保存锁对应的唯一标识
private static final ThreadLocal<String> LOCK_FLAG = ThreadLocal.withInitial(() ->
UUID.randomUUID().toString().replace("-", "").toLowerCase()
);
// 尝试加锁
private boolean tryLock(String key, long ttl) {
try {
String val = LOCK_FLAG.get();
Boolean lockRes = redisTemplate.opsForValue()
.setIfAbsent(key, val, ttl, TimeUnit.MILLISECONDS);
log.debug("tryLock, key={}, val={}, lockRes={}", key, val, lockRes);
return Boolean.TRUE.equals(lockRes);
} catch (Exception e) {
log.error("tryLock occurred an exception", e);
}
return false;
}
// 解锁
public boolean unlock(String key) {
boolean succeed = false;
try {
List<String> keys = Collections.singletonList(key);
Object[] args = {LOCK_FLAG.get()};
Long unlockRes = redisTemplate.execute(UNLOCK_SCRIPT, keys, args);
log.debug("unlock, key={}, args={}, unlockRes={}", key, args, unlockRes);
succeed = Optional.ofNullable(unlockRes).filter(res -> res > 0).isPresent();
} catch (Exception e) {
log.error("unlock occurred an exception", e);
} finally {
if (succeed) {
LOCK_FLAG.remove();
}
}
return succeed;
}
Lua 脚本
local lock_key = KEYS[1];
local lock_flag = ARGV[1];
--- 判断锁定的唯一标识与参数一致删除锁
--- 返回值:1=解锁成功(删除成功),0=锁已失效或删除失败,-1=非自己的锁不支持解锁
local val = redis.call('GET', lock_key);
if (not val) then
return 0;
elseif (val == lock_flag) then
return redis.call('DEL', lock_key);
else
return -1;
end
缺陷
使用 ThreadLocal 保存 锁key 与 相应的唯一标识
加锁:使用 HASH 保存锁标识与加锁次数
解锁:Lua 脚本判断是自己加的锁进行释放
功能:可重入(Redis HASH)、支持对不同 key 进行加解锁(ThreadLocal<Map<String, String>>)
工具类
// 使用 ThreadLocal 保存 锁key 与 唯一标识
private static final ThreadLocal<Map<String, String>> LOCK_FLAG =
ThreadLocal.withInitial(HashMap::new);
// 尝试加锁
private long tryLock(String key, long ttl) {
String uniqueFlag = LOCK_FLAG.get().get(key);
if (uniqueFlag == null) {
uniqueFlag = UUID.randomUUID().toString().replace("-", "");
LOCK_FLAG.get().put(key, uniqueFlag);
}
try {
List<String> keys = Collections.singletonList(key);
Object[] args = {uniqueFlag, ttl};
Long lockRes = redisTemplate.execute(LOCK_SCRIPT, keys, args);
log.debug("tryLock, lock_flag={}, key={}, args={}, lockRes={}",
LOCK_FLAG.get(), key, args, lockRes);
return lockRes != null ? lockRes : 0L;
} catch (Exception e) {
log.error("tryLock occurred an exception", e);
}
return 0L;
}
// 尝试解锁
public long tryUnlock(String key) {
String uniqueFlag = LOCK_FLAG.get().get(key);
if (uniqueFlag == null) {
return 0L;
}
long lockNum = -1L;
try {
List<String> keys = Collections.singletonList(key);
Object[] args = {uniqueFlag};
Long unlockRes = redisTemplate.execute(UNLOCK_SCRIPT, keys, args);
log.debug("unlock, key={}, args={}, unlockRes={}", key, args, unlockRes);
lockNum = unlockRes != null ? unlockRes : 0L;
} catch (Exception e) {
log.error("release lock occurred an exception", e);
} finally {
if (lockNum == 0L) {
LOCK_FLAG.get().remove(key);
if (LOCK_FLAG.get().isEmpty()) {
LOCK_FLAG.remove();
}
}
}
return lockNum;
}
Lua 脚本
```lua
local lock_key = KEYS[1];
local lock_flag = ARGV[1];
--- 锁定时长,单位:毫秒
local lock_ttl = tonumber(ARGV[2]);
--- HASH 支持可重入
--- lock_flag 保存加锁唯一标识
--- lock_num 保存加锁次数
local info = redis.call("HMGET", lock_key, "lock_flag", "lock_num");
local h_flag = info[1];
local h_num = tonumber(info[2]);
if (h_num == nil or h_num < 0) then
h_num = 0;
end
--- 返回加锁次数,未加锁成功返回 -1
if (not h_flag or h_flag == lock_flag) then
local res_num = h_num + 1;
redis.call("HMSET", lock_key, "lock_flag", lock_flag, "lock_num", res_num);
redis.call("PEXPIRE", lock_key, lock_ttl);
return res_num;
else
return -1;
end
```
```lua
local lock_key = KEYS[1];
local lock_flag = ARGV[1];
--- HASH 支持可重入
--- lock_flag 保存加锁唯一标识
--- lock_num 保存加锁次数
local info = redis.call("HMGET", lock_key, "lock_flag", "lock_num");
local h_flag = info[1];
local h_num = tonumber(info[2]);
if (h_num == nil) then
h_num = 0;
end
--- 返回剩余加锁次数,未被加锁或解锁完返回 0,非自己加锁返回 -1
if (not h_flag) then
return 0;
elseif (h_flag == lock_flag) then
if (h_num <= 0) then
redis.call("DEL", lock_key);
return 0;
else
local res_num = h_num - 1;
redis.call("HMSET", lock_key, "lock_flag", lock_flag, "lock_num", res_num);
return res_num;
end
else
return -1;
end
```
demo 地址:https://github.com/EastX/java-practice-demos/tree/main/demo-lock
我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功
我正在尝试上传文件。一个简单的hello.txt。我正在关注文档,但无法将其上传到我的存储桶。#STARTAWSCLIENTs3=Aws::S3::Resource.newbucket=s3.bucket(BUCKET_NAME)begins3.buckets[BUCKET_NAME].objects[KEY].write(:file=>FILE_NAME)puts"Uploadingfile#{FILE_NAME}tobucket#{BUCKET_NAME}."bucket.objects.eachdo|obj|puts"#{obj.key}=>#{obj.etag}"endresc
我有一个Highstock图表(带有标记和阴影的线条),并且想以编程方式显示一个highstock工具提示,例如,当我选择某个表上的一行(包含图表数据)我想显示相应的highstock工具提示。这可能吗? 最佳答案 股票图表thissolution不起作用:在thisexample你必须更换这个:chart.tooltip.refresh(chart.series[0].data[i]);为此:chart.tooltip.refresh([chart.series[0].points[i]]);解决方案可用here.
一、机器人介绍 此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接
我是syslog的新手。我们决定使用系统日志来跟踪Rails应用程序中的一些特殊事件。问题是我不想使用默认的/var/log/system.log文件,而是使用自定义文件,例如/var/log/myapp_events.log.我看到我必须像这样在/etc/syslog.conf中定义我自己的设施:myapp_events.*/var/log/myapp_events.log重新启动syslogd后,我发现我可以直接在bash控制台中使用它:syslog-s-kFacilitymyapp_eventsMessage"thisismymessage"该消息按预期出现在/var/log/m
我有一个启动DRb服务的脚本,然后生成处理程序对象并通过DRb.thread.join等待。我希望脚本一直运行直到被明确杀死,所以我添加了trap"INT"doDRb.stop_serviceend在Ruby1.8下成功停止DRb服务并退出,但在1.9下似乎死锁(在OSX10.6.7上)。对该进程进行采样显示在semaphore_wait_signal_trap中有几个线程在旋转。我假设我在调用stop_service时做错了什么,但我不确定是什么。谁能给我任何关于如何正确处理它的指示? 最佳答案 好的,我想我已经找到了解决方案。如