一、源码解析
1、加读锁
代码名词解释:
- (1)KEYS[1]:this.getName()。读写锁的名称。用户自己定义的。
- (2)KEYS[2]:this.getReadWriteTimeoutNamePrefix(threadId)。记录了持有锁的所有线程。
- (3)ARGV[1]:this.internalLockLeaseTime。锁的过期时间。默认为30s。
- (4)ARGV[2]:this.getLockName(threadId)。线程与锁绑定的名称。
- (5)ARGV[3]:this.getWriteLockName(threadId)。线程写锁的名称。
//定义mode为名称是KEYS[1]的锁的形式。 mode有两个值:read, write。
//查询锁
local mode = redis.call('hget', KEYS[1], 'mode');
//锁不存在
if (mode == false)
then
//创建读写锁
redis.call('hset', KEYS[1], 'mode', 'read');
//为当前线程threadId加锁,value=1
redis.call('hset', KEYS[1], ARGV[2], 1);
//为当前线程设置一个管理超时时间的key
redis.call('set', KEYS[2] .. ':1', 1);
//为当前线程管理超时时间的key设置过期时间
redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]);
//为读写锁设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
//读锁已存在或者当前线程已持有写锁
if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1)
then
//为当前线程的读写锁的value+1。
//ind 加锁次数
local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1);
//所有持有锁的线程的总次数
local key = KEYS[2] .. ':' .. ind;
//设置key,表示锁被占用中
redis.call('set', key, 1);
//设置key的过期时间
redis.call('pexpire', key, ARGV[1]);
//设置读写锁的过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
//获取锁失败,返回读写锁的过期时间
return redis.call('pttl', KEYS[1]);
2、加写锁
代码名词解释:
- (1)KEYS[1]:this.getName()。读写锁的名称。用户自己定义的。
- (2)ARGV[1]:this.internalLockLeaseTime。锁的过期时间。默认为30s。
- (3)ARGV[2]:this.getLockName(threadId)。线程与锁绑定的名称。
local mode = redis.call('hget', KEYS[1], 'mode');
//锁未开启
if (mode == false)
then
//设置锁的形式为写锁
redis.call('hset', KEYS[1], 'mode', 'write');
//为锁绑定当前线程,加锁次数为1
redis.call('hset', KEYS[1], ARGV[2], 1);
//为锁设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
//如果当前锁为写锁
if (mode == 'write')
then
//如果写锁绑定的是当前线程,并且加锁次数是1
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
then
//为当前线程的加锁次数+1
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]);
3、释放读锁
代码名词解释:
- (1)KEYS[1]:this.getName()。读写锁的名称。用户自己定义的。
- (2)KEYS[2]:this.getChannelName()。发送消息的channel name。
- (3)ARGV[1]:LockPubSub.READ_UNLOCK_MESSAGE。释放读锁的消息。
- (4)ARGV[2]:this.internalLockLeaseTime。锁的过期时间。
- (5)ARGV[3]:this.getLockName(threadId)。线程与锁绑定的名称。
local mode = redis.call('hget', KEYS[1], 'mode');
//如果锁未开启
if (mode == false)
then
//发送释放读锁的消息到channel
redis.call('publish', KEYS[2], ARGV[1]);
//返回1,表示发送消息成功
return 1;
end;
//如果当前是写锁
if (mode == 'write')
then
local lockExists = redis.call('hexists', KEYS[1], ARGV[3]);
//如果是当前线程不持有锁
if (lockExists == 0)
then
return nil;
else
//当前线程持有读锁,将持有锁的数量减1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
//当前线程还持有锁
if (counter > 0)
then
//设置锁的过期时间
redis.call('pexpire', KEYS[1], ARGV[2]);
//返回0,表示当前线程仍然有未释放的锁
return 0;
else
//counter==0,当前线程不持有锁
//将线程与锁解除绑定关系
redis.call('hdel', KEYS[1], ARGV[3]);
//如果读写锁的域的数量等于1,也就是只剩下mode域,没有任何线程持有锁
if (redis.call('hlen', KEYS[1]) == 1)
then
//删除锁
redis.call('del', KEYS[1]);
////发送过期时间的消息到channel
redis.call('publish', KEYS[2], ARGV[1]);
else
////将锁的形式改为读锁
redis.call('hset', KEYS[1], 'mode', 'read');
end;
//返回1,表示当前线程已释放锁
return 1;
end;
end;
end;
return nil;
4、释放写锁
代码名词解释:
- (1)KEYS[1]:this.getName()。读写锁的名称。用户自己定义的。
- (2)KEYS[2]:this.getChannelName()。发送消息的channel name。
- (3)ARGV[1]:LockPubSub.READ_UNLOCK_MESSAGE。释放读锁的消息。
- (4)ARGV[2]:this.internalLockLeaseTime。锁的过期时间。
- (5)ARGV[3]:this.getLockName(threadId)。线程与锁绑定的名称。
local mode = redis.call('hget', KEYS[1], 'mode');
//如果锁未开启
if (mode == false)
then
//发送释放读锁的消息到channel
redis.call('publish', KEYS[2], ARGV[1]);
//返回1,表示发送消息成功
return 1;
end;
//如果当前锁是写锁
if (mode == 'write')
then
local lockExists = redis.call('hexists', KEYS[1], ARGV[3]);
//如果是当前线程不持有写锁
if (lockExists == 0)
then
//返回nil,表示当前线程并不持有锁
return nil;
else
//当前线程持有写锁,将持有锁的数量减1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
//如果当前线程还持有锁
if (counter > 0)
then
//设置锁的过期时间
redis.call('pexpire', KEYS[1], ARGV[2]);
//返回0,表示当前线程仍然有未释放的锁
return 0;
else
//counter==0,当前线程不持有锁
//将线程与锁解除绑定关系
redis.call('hdel', KEYS[1], ARGV[3]);
//如果读写锁的域的数量等于1,也就是只剩下mode域,没有任何线程持有锁
if (redis.call('hlen', KEYS[1]) == 1)
then
//删除锁
redis.call('del', KEYS[1]);
//发送过期时间的消息到channel
redis.call('publish', KEYS[2], ARGV[1]);
else
//将锁的形式改为读锁
redis.call('hset', KEYS[1], 'mode', 'read');
end;
//返回1,表示当前线程已释放锁
return 1;
end;
end;
end;
return nil;
二、对锁的理解
1、锁的域
锁分为mode域和线程域。mode域中有read\write两种值。线程域中放的是每个加锁的ThreadId。