Redisson的分布式锁RReadWriteLock详解

141 阅读4分钟

一、源码解析

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。