上两篇文章,我们已经分析了读锁 RedissonReadLock 的加锁和释放锁的执行原理。下面,我们直入主题,将先分析写锁 RedissonWriteLock 的加锁原理,至于 watchdog 机制中的 lua 脚本,RedissonWriteLock 和 RedissonLock 保持一致,不需和 RedissonReadLock 一样单独分析。
RedissonWriteLock#tryLockInnerAsync:
@Override
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> 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.<Object>asList(getName()),
internalLockLeaseTime, getLockName(threadId));
}
我们可以看到,写锁获取锁的 lua 脚本不长,我们一步一步分析。
分析前,我们先定好,读写锁的key为 myLock:
RedissonClient client = RedissonClientUtil.getClient("");
RReadWriteLock readWriteLock = client.getReadWriteLock("myLock");
Arrays.asList(getName()):
KEYS:[“myLock”]
internalLockLeaseTime, getLockName(threadId):
ARGVS:[30_000毫秒,“UUID:threadId:write”]
lua脚本
local mode = redis.call('hget', KEYS[1], 'mode');
分析:
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:write 1
执行后,锁的内容如下:
myLock:{
"mode":"write",
"UUID:threadId:write":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:write
利用 hincrby 命令为当前线程增加1次加锁次数
hincrby myLock UUID:threadId:write 1
假设之前当前线程获取1次写锁,那么执行后,redis里写锁的相关数据:
myLock:{
"mode":"write",
"UUID:threadId:write":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 基本保持一致。