Redisson源码学习之RedissonFairLock
博客待整理,先只是把源码看了....
后面不再备注redis中的命令含义了,这样备注写太多了不好阅读.
package org.redisson;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.command.CommandExecutor;
import org.redisson.pubsub.LockPubSub;
/**
* Distributed implementation of {@link java.util.concurrent.locks.Lock}
* Implements reentrant lock.<br>
* Lock will be removed automatically if client disconnects.
* <p>
* Implements a <b>fair</b> locking so it guarantees an acquire order by threads.
*
* @author Nikita Koksharov
*
*/
/** vergilyn mark: <br/>
* remove stale threads: 为什么不写成公共的? <br/>
* redisson是如何解决服务器之间时间不同步的: 因为{Redisson.UUID}, 不同服务器生成UUID不同, 所以其时间戳的值肯定会来至原有的服务器. <br/>
* "redisson_lock_queue:{lock_name}": 这LIST的顺序就是获取锁的顺序, 即FairLock实现原理. <br/>
* "redisson_lock_timeout:{xxx}": 这其中的score并不表示获取锁的顺序, 而是表示线程竞争锁的失效时间点. <br/>
*/
public class RedissonFairLock extends RedissonLock implements RLock {
private final long threadWaitTime = 5000;
private final CommandExecutor commandExecutor;
/** vergilyn mark: <br/>
* 构造函数, 被protected修饰, 说明不能在外部通过new获取; 一般的构建是:{@link Redisson#getFairLock(String)}. <br/>
* @param name lock的'锁名', 实际定义在{@link RedissonObject#name}
* @param id
*/
protected RedissonFairLock(CommandExecutor commandExecutor, String name, UUID id) {
super(commandExecutor, name, id);
this.commandExecutor = commandExecutor;
}
/** vergilyn mark: <br/>
* 未被修饰符修饰, 默认即friendly: 在同一个包中的类可以访问, 其他包中的类不能访问. <br/>
* type : LIST <br/>
* key : "redisson_lock_queue:{lock_name}" <br/>
* value: "{Redisson.UUID}:{threadId}" <br/>
* @return ex, redisson_lock_queue:{lock_name}
*/
String getThreadsQueueName() {
return prefixName("redisson_lock_queue", getName());
}
/** vergilyn mark: <br/>
* type : SORT-SET <br/>
* key : "redisson_lock_timeout:{lock_name}" <br/>
* value: "{Redisson.UUID}:{threadId}" <br/>
* score: 时间戳 <br/>
* @return ex, redisson_lock_timeout:{lock_name}
*/
String getTimeoutSetName() {
return prefixName("redisson_lock_timeout", getName());
}
@Override
protected RedissonLockEntry getEntry(long threadId) {
return PUBSUB.getEntry(getEntryName() + ":" + threadId);
}
/** vergilyn mark: <br/>
* 订阅, 重写: {@link RedissonLock#subscribe(long)}
*/
@Override
protected RFuture<RedissonLockEntry> subscribe(long threadId) {
return PUBSUB.subscribe(getEntryName() + ":" + threadId,
getChannelName() + ":" + getLockName(threadId), commandExecutor.getConnectionManager());
}
/** vergilyn mark: <br/>
* 取消订阅, 重写: {@link RedissonLock#unsubscribe(RFuture, long)}
*/
@Override
protected void unsubscribe(RFuture<RedissonLockEntry> future, long threadId) {
PUBSUB.unsubscribe(future.getNow(), getEntryName() + ":" + threadId,
getChannelName() + ":" + getLockName(threadId), commandExecutor.getConnectionManager());
}
@Override
protected RFuture<Void> acquireFailedAsync(long threadId) {
/* vergilyn mark: lua中数组下标是从1开始
* lindex key index : LIST, 返回列表中下标为指定索引值的元素. 如果指定索引值不在列表的区间范围内, 返回 nil.
* zrange key start stop [WITHSCORES] : SORT-SET, 通过索引区间返回有序集合成指定区间内的成员.
* zincrby key increment member : SORT-SET, 有序集合中对指定成员的分数加上增量 increment.
* zrem key member [member ...] : SORT-SET, 移除有序集合中的一个或多个成员
* lrem key count value : LIST, 移除列表元素(count=0, 表示全部)
*/
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
// KEYS[1]: "redisson_lock_queue:{xxx}", ARGV[1]: "{Redisson.UUID}:{threadId}"
// KEYS[2]: "redisson_lock_timeout:{xxx}", ARGV[2]: "{threadWaitTime}"(默认: 5000ms)
"local firstThreadId = redis.call('lindex', KEYS[1], 0); " +
"if firstThreadId == ARGV[1] then " +
"local keys = redis.call('zrange', KEYS[2], 0, -1); " +
"for i = 1, #keys, 1 do " +
"redis.call('zincrby', KEYS[2], -tonumber(ARGV[2]), keys[i]);" +
"end;" +
"end;" +
"redis.call('zrem', KEYS[2], ARGV[1]); " +
"redis.call('lrem', KEYS[1], 0, ARGV[1]); ",
Arrays.<Object>asList(getThreadsQueueName(), getTimeoutSetName()), // KEYS
getLockName(threadId), threadWaitTime); // PARAMS
}
/** vergilyn mark: <br/>
* FairLock核心方法, 尝试获取锁(内部异步获取);
* remark: 因为{Redisson.UUID}, 解决了服务器之间的时间不同步
* @param leaseTime 锁自动释放的时长
* @param unit leaseTime的时间单位
* @param threadId 线程
* @return
*/
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
long currentTime = System.currentTimeMillis();
if (command == RedisCommands.EVAL_NULL_BOOLEAN) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// remove stale threads: 移除陈旧的线程
// KEYS[1]: "lock_name", ARGV[1]: "{leaseTime}"
// KEYS[2]: "redisson_lock_queue:{xxx}", ARGV[2]: "{Redisson.UUID}:{threadId}"
// KEYS[3]: "redisson_lock_timeout:{xxx}", ARGV[3]: "{currentTime}" (部署服务器的时间ms, 不是redis-server的服务器时间)
"while true do "
+ "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[3]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
+
"if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) "
+ "or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then " +
"redis.call('lpop', KEYS[2]); " +
"redis.call('zrem', KEYS[3], ARGV[2]); " +
"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 1;",
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName()), // KEYS
internalLockLeaseTime, getLockName(threadId), currentTime); // PARAMS
}
if (command == RedisCommands.EVAL_LONG) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// remove stale threads: 移除无效的竞争锁的线程
// KEYS[1]: "lock_name", ARGV[1]: "{leaseTime}"
// KEYS[2]: "redisson_lock_queue:{xxx}", ARGV[2]: "{Redisson.UUID}:{threadId}"
// KEYS[3]: "redisson_lock_timeout:{xxx}", ARGV[3]: "{currentTime + threadWaitTime}" (部署服务器的时间ms, 不是redis-server的服务器时间)
// ARGV[4]: "{currentTime}"
// 移除竞争lock_name中无效的线程
"while true do "
+ "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[4]) then " // 如果存在且已失效, 则相应的从"redisson_lock_queue:{xxx}"、"redisson_lock_timeout:{xxx}"中移除
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
// exists: 若 key 存在返回 1 ,否则返回 0 。
// 1. KEYS[1]: 记录获取到lock_name的线程, 及记录线程获取锁的次数. (此HASH只会存在一个field)
// type: HASH, key: "lock_name", field: "{Redisson.UUID}:{threadId}", value: 1 (value表示线程获取锁的次数, 释放锁时必须释放相同的次数, 才会释放锁lock_name)
// 2. 当不存在"redisson_lock_queue:{lock_name}"时, 表示没有竞争对手存在; 或, 竞争队列的第一个值为当前线程 (因为是FairLock, 所以要满足 redis.call('lindex', KEYS[2], 0) == ARGV[2])
+ "if (redis.call('exists', KEYS[1]) == 0) and ((redis.call('exists', KEYS[2]) == 0) "
+ "or (redis.call('lindex', KEYS[2], 0) == ARGV[2])) then " +
// 当前线程获取到锁, 从queue、timeout中移除
"redis.call('lpop', KEYS[2]); " +
"redis.call('zrem', KEYS[3], ARGV[2]); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " + // 标记当前锁已被某个线程获取
"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 设置标记的失效时常, 默认是 30 * 1000 ms
"return nil; " +
"end; " +
// (重复获取锁) 当前线程即持有锁的线程, 即可重入锁(Reentrant Lock)
"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; " +
"local firstThreadId = redis.call('lindex', KEYS[2], 0); " +
"local ttl; " +
"if firstThreadId ~= false and firstThreadId ~= ARGV[2] then " + // 队列存在, 且队列的第一个值 ≠ 当前线程
"ttl = tonumber(redis.call('zscore', KEYS[3], firstThreadId)) - tonumber(ARGV[4]);" +
"else "
+ "ttl = redis.call('pttl', KEYS[1]);" +
"end; " +
"local timeout = ttl + tonumber(ARGV[3]);" + // 计算竞争线程的失效时间点
"if redis.call('zadd', KEYS[3], timeout, ARGV[2]) == 1 then " + // 设置当前线程竞争锁的失效时间点
"redis.call('rpush', KEYS[2], ARGV[2]);" + // 将当前线程加入竞争锁的队列中
"end; " +
"return ttl;", // 返回持有锁的剩余时长,
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName()),
internalLockLeaseTime, getLockName(threadId), currentTime + threadWaitTime, currentTime);
}
throw new IllegalArgumentException();
}
/** vergilyn mark: <br/>
* 释放锁(内部异步); 只有持有锁的线程才能释放锁, 且线程获取多少次锁, 就要释放多少次, 否则不会释放(除非锁达到失效时长).
* lua执行返回: 1, 成功释放锁; nil/0, 未释放任何锁.
* @see RedissonFairLock#forceUnlockAsync()
*/
@Override
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// remove stale threads: 移除无效的获取锁的线程
"while true do "
+ "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[4]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
// 锁已释放(KEYS[1]已自动失效), 通知下一个获取锁的线程
+ "if (redis.call('exists', KEYS[1]) == 0) then " +
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; " + // 成功释放锁
"end;" +
// 当前线程不是持有锁的线程, 不允许释放锁
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
// 当前线程是持有锁的线程, value: 递减
// (ReentrantLock概念) 当同一线程中多次获取锁, 必须释放相同多的次数, 才会最终释放锁
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " + // 每次递减后重置失效时长
"return 0; " + // 释放锁失败
"end; " +
// 当前线程所有获取锁的地方都释放了锁, 则正确的释放锁
"redis.call('del', KEYS[1]); " +
// 通知下一个竞争相同锁的线程.
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; ", // 成功释放锁
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName(), getChannelName()),
LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId), System.currentTimeMillis());
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
@Override
public RFuture<Boolean> deleteAsync() {
return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getThreadsQueueName(), getTimeoutSetName());
}
/** vergilyn mark: <br/>
* 强制释放锁
* @see RedissonLock#unlockInnerAsync
*/
@Override
public RFuture<Boolean> forceUnlockAsync() {
cancelExpirationRenewal();
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// remove stale threads
"while true do "
+ "local firstThreadId2 = redis.call('lindex', KEYS[2], 0);"
+ "if firstThreadId2 == false then "
+ "break;"
+ "end; "
+ "local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));"
+ "if timeout <= tonumber(ARGV[2]) then "
+ "redis.call('zrem', KEYS[3], firstThreadId2); "
+ "redis.call('lpop', KEYS[2]); "
+ "else "
+ "break;"
+ "end; "
+ "end;"
+
// 强制释放锁, 即使当前线程不是持有锁的线程, 并通知下一个竞争相同锁的线程
"if (redis.call('del', KEYS[1]) == 1) then " +
"local nextThreadId = redis.call('lindex', KEYS[2], 0); " +
"if nextThreadId ~= false then " +
"redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); " +
"end; " +
"return 1; " + // 强制释放锁成功
"end; " +
"return 0;", // 强制释放锁失败
Arrays.<Object>asList(getName(), getThreadsQueueName(), getTimeoutSetName(), getChannelName()),
LockPubSub.unlockMessage, System.currentTimeMillis());
}
}
Redisson源码学习之RedissonFairLock的更多相关文章
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- jQuery源码学习感想
还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
- 我的angularjs源码学习之旅2——依赖注入
依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...
- ddms(基于 Express 的表单管理系统)源码学习
ddms是基于express的一个表单管理系统,今天抽时间看了下它的代码,其实算不上源码学习,只是对它其中一些小的开发技巧做一些记录,希望以后在项目开发中能够实践下. 数据层封装 模块只对外暴露mod ...
- leveldb源码学习系列
楼主从2014年7月份开始学习<>,由于书籍比较抽象,为了加深思考,同时开始了Google leveldb的源码学习,主要是想学习leveldb的设计思想和Google的C++编程规范.目 ...
随机推荐
- 理想乡题解 (线段树优化dp)
题面 思路概述 首先,不难想到本题可以用动态规划来解,这里就省略是如何想到动态规划的了. 转移方程 f[i]=min(f[j]+1)(max(i-m,0)<=j<i 且j符合士兵限定) 注 ...
- JVM第一弹
JVM第一弹 基本概念 JVM是可运行java代码的假想计算机,包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收.堆和一个存储方法域.JVM是运行在操作系统之上的,它与硬件没有直接的交互. 运行 ...
- oc---类方法load和initialize的区别
在iOS开发中,就像Application有生命周期回调方法一样,在Objective-C的类被加载和初始化的时候,也可以收到方法回调,可以在适当的情况下做一些定制处理.而这正是本篇文章所要介绍的lo ...
- LeetCode 380. Insert Delete GetRandom O(1) 常数时间插入、删除和获取随机元素(C++/Java)
题目: Design a data structure that supports all following operations in averageO(1) time. insert(val): ...
- 暑假第一周总结(在centos虚拟机上安装jdk以及hadoop并对hadoop进行配置)
本周主要就是对虚拟机进行安装并在上边安装jdk以及hadoop并对其进行配置. 在看林子雨老师的教程时,下载了老师所给的全套的下载软件,在安装时发现老师所给的VirtualBox安装后无法正常启动,尝 ...
- usaco1.1
Your Ride Is Here #include <iostream> #include <string> #include <vector> using na ...
- Codeforces 1065C Make It Equal (差分+贪心)
题意:n个塔,第i个塔由$h_i$个cube组成,每次可以切去某高度h以上的最多k个cube,问你最少切多少次,可以让所有塔高度相等 k>=n, n<=2e5 思路:差分统计每个高度i有的 ...
- 机器学习(ML)十三之批量归一化、RESNET、Densenet
批量归一化 批量归一化(batch normalization)层,它能让较深的神经网络的训练变得更加容易.对图像处理的输入数据做了标准化处理:处理后的任意一个特征在数据集中所有样本上的均值为0.标准 ...
- 对MYSQL注入相关内容及部分Trick的归类小结
前言 最近在给学校的社团成员进行web安全方面的培训,由于在mysql注入这一块知识点挺杂的,入门容易,精通较难,网上相对比较全的资料也比较少,大多都是一个比较散的知识点,所以我打算将我在学习过程中遇 ...
- Mac设置Linux免密登陆
利用公钥认证登录 1.创建共钥 输入下面的命令,一路回车 ssh-keygen -t rsa 2.复制公钥到ssh服务器 将上一步生成的id_rsa.pub公钥文件复制到目标服务器对应用户下的~/.s ...