上两篇文章,我们已经分析了读锁 RedissonReadLock 的加锁和释放锁的执行原理。下面,我们直入主题,将先分析写锁 RedissonWriteLock 的加锁原理,至于 watchdog 机制中的 lua 脚本,RedissonWriteLock 和 RedissonLock 保持一致,不需和 RedissonReadLock 一样单独分析。
RedissonWriteLock#tryLockInnerAsync:
@OverrideRFuture tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) { internalLockLeaseTime = unit.toMillis(leaseTime); return evalWriteAsync(getName(), LongCodec.INSTANCE, command, "local mode = redis.call('hget', KEYS[1], 'mode'); " + "if (mode == false) then " + "redis.call('hset', KEYS[1], 'mode', 'write'); " + "redis.call('hset', KEYS[1], ARGV[2], 1); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + "if (mode == 'write') then " + "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + "local currentExpire = redis.call('pttl', KEYS[1]); " + "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " + "return nil; " + "end; " + "end;" + "return redis.call('pttl', KEYS[1]);", Arrays.
我们可以看到,写锁获取锁的 lua 脚本不长,我们一步一步分析。
分析前,我们先定好,读写锁的key为 myLock:
RedissonClient client = RedissonClientUtil.getClient(""); RReadWriteLock readWriteLock = client.getReadWriteLock("myLock");1.1、KEYS
Arrays.asList(getName()):
- getName(): 锁key
KEYS:[“myLock”]
1.2、ARGVSinternalLockLeaseTime, getLockName(threadId):
- internalLockLeaseTime:其实就是 watchdog 的超时时间,默认是30000毫秒,可看 Config#lockWatchdogTimeout。
- getLockName(threadId):super.getLockName(threadId) + “:write” -> 客户端ID(UUID):线程ID(threadId):write
ARGVS:[30_000毫秒,“UUID:threadId:write”]
1.3、lua 脚本分析 1、第一步,获取锁模式lua脚本
local mode = redis.call('hget', KEYS[1], 'mode');
分析:
- 利用 hget 命令获取当前锁模式
hget myLock mode
场景:
- 当前线程尝试获取写锁时,还没有其他线程成功持有锁,包括读锁和写锁
lua脚本:
"if (mode == false) then " + "redis.call('hset', KEYS[1], 'mode', 'write'); " + "redis.call('hset', KEYS[1], ARGV[2], 1); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " +
分析:
-
利用 hset 命令设置锁模式为写锁
hset myLock mode write
执行后,锁内容如下:
myLock:{ "mode":"write" }
-
利用 hset 命令为当前线程添加加锁次数记录
hset myLock UUID:threadId 1
执行后,锁的内容如下:
myLock:{ "mode":"write", "UUID:threadId":1 }
我们可以发现,读写锁中的写锁获取锁不再需要写锁中的加锁超时记录,因为写锁仅支持一个线程来持有锁,锁的超时时间就是线程持有锁的超时时间。
-
利用 pexpire 命令为锁添加过期时间
pexpire myLock 30000
-
最后返回nil,表示获取锁成功
场景:
- 当前线程重复获取写锁,即读写锁中的写锁支持可重入获取锁
lua脚本:
"if (mode == 'write') then " + "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + "local currentExpire = redis.call('pttl', KEYS[1]); " + "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " + "return nil; " + "end; " + "end;"
分析:
-
锁模式为写锁,利用 hexists 命令判断持有写锁为当前线程
hexists myLock UUID:threadId
-
利用 hincrby 命令为当前现成增加1次加锁次数
hincrby myLock UUID:threadId 1
假设之前当前线程获取1次写锁,那么执行后,redis里写锁的相关数据:
myLock:{ "mode":"write", "UUID:threadId":2 }
我们可以看到,读写锁里面写锁在 redis 里面的数据,和 RedissonLock 相比,只多了一个mode字段来标识当前读写锁的模式;当然了,写锁也支持相同线程可重入获取锁。
-
利用 pttl 获取当前写锁的超时剩余毫秒数
pttl myLock
-
利用 pexipre 给锁重新设置锁的过期时间,过期时间为:上次加锁的剩余毫秒数+30000毫秒
pexpire myLock currentExpire+30000
-
最后返回nil,表示获取锁成功
场景:
- 不满足上面的两个分支,当前线程就无法成功获取写锁
lua脚本:
"return redis.call('pttl', KEYS[1]);"
分析:
-
利用 pttl 命令获取锁过期时间(毫秒)
pttl myLock
-
直接返回步骤1的获取到的毫秒数
因为 RedissonWriteLock 也是基于 RedissonLock 扩展的,所以关于 watchdog 和获取锁失败等机制,就不再详述了,和 RedissonLock 基本保持一致。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)