【Redisson】读写锁 锁源码剖析(二)

132 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

锁的升降级

1. 为什么需要锁的降级?

答案:提高整体性能。

如果一直使用写锁,最后才释放写锁的话,虽然确实是线程安全的,但是也是没有必要的,因为只有一处修改数据的代码:

data = new Object();

后面对于 data 仅仅是读取。

如果还一直使用写锁的话,就不能让多个线程同时来读取了,持有写锁是浪费资源的,降低了整体的效率,所以这个时候利用锁的降级是很好的办法,可以提高整体性能。

2. 支持锁的降级,不支持升级。为什么不支持锁的升级?

答案:会产生死锁。

如果运行下面这段代码,在不释放读锁的情况下直接尝试获取写锁,也就是锁的升级,会让线程直接阻塞,程序是无法运行的。

final static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 
public static void main(String[] args) {
    upgrade();
}
 
public static void upgrade() {
    rwl.readLock().lock();
    System.out.println("获取到了读锁");
    rwl.writeLock().lock();
    System.out.println("成功升级");
}

这段代码会打印出“获取到了读锁”,但是却不会打印出“成功升级”,因为 ReentrantReadWriteLock 不支持读锁升级到写锁。

读写锁的特点是:

  • 如果线程都申请读锁,是可以多个线程同时持有的。
  • 如果是写锁,只能有一个线程持有,并且不可能存在读锁和写锁同时持有的情况。

正是因为不可能有读锁和写锁同时持有的情况,所以升级写锁的过程中,需要等到所有的读锁都释放,此时才能进行升级。

源码剖析 - 释放锁

1)读锁 - 释放锁

加锁对应源码位于: RedissonReadLock#unlockInnerAsync

对应参数如下:

  • KEYS[1]:锁的名称 "rwLock"
  • KEYS[2]:通道名称,redisson_rwlock:{锁名}
  • KEYS[3]{锁名}:UUID:threadId:rwlock_timeout
  • KEYS[4]{锁名}:UUID:threadId
  • ARGV[1]LockPubSub.READ_UNLOCK_MESSAGE,需要通知的个数
  • ARGV[2]:线程key 名称,UUID:threadId
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1;
end;
local lockExists = redis.call('hexists', KEYS[1], ARGV[2]);
if (lockExists == 0) then 
    return nil;
end;
​
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1);
if (counter == 0) then
    redis.call('hdel', KEYS[1], ARGV[2]);
end;
redis.call('del', KEYS[3] .. ':' .. (counter+1));
​
if (redis.call('hlen', KEYS[1]) > 1) then 
    local maxRemainTime = -3; 
    local keys = redis.call('hkeys', KEYS[1]); 
    for n, key in ipairs(keys) do 
        counter = tonumber(redis.call('hget', KEYS[1], key)); 
        if type(counter) == 'number' then 
            for i=counter, 1, -1 do 
                local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i);
                maxRemainTime = math.max(remainTime, maxRemainTime);
            end; 
        end; 
    end; 
​
    if maxRemainTime > 0 then 
        redis.call('pexpire', KEYS[1], maxRemainTime); 
        return 0; 
    end;
​
    if mode == 'write' then 
        return 0;
    end; 
end; 
​
redis.call('del', KEYS[1]); 
redis.call('publish', KEYS[2], ARGV[1]); 
return 1; 

2)写锁 - 释放锁

加锁对应源码位于: RedissonWriteLock#unlockInnerAsync

对应参数如下:

  • KEYS[1]:锁的名称 "rwLock"
  • KEYS[2]:通道名称,redisson_rwlock:{锁名}
  • ARGV[1]LockPubSub.READ_UNLOCK_MESSAGE,需要通知的个数
  • ARGV[2]:锁时间,默认 30秒
  • ARGV[3]:线程锁名key 名称,UUID:threadId:write
local mode = redis.call('hget', KEYS[1], 'mode'); 
if (mode == false) then 
    redis.call('publish', KEYS[2], ARGV[1]); 
    return 1; 
end;
if (mode == 'write') then 
    local lockExists = redis.call('hexists', KEYS[1], ARGV[3]); 
    if (lockExists == 0) then 
        return nil;
    else 
        local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
        if (counter > 0) then 
            redis.call('pexpire', KEYS[1], ARGV[2]); 
            return 0; 
        else 
            redis.call('hdel', KEYS[1], ARGV[3]); 
            if (redis.call('hlen', KEYS[1]) == 1) then 
                redis.call('del', KEYS[1]); 
                redis.call('publish', KEYS[2], ARGV[1]); 
            else
                // has unlocked read-locks
                redis.call('hset', KEYS[1], 'mode', 'read'); 
            end; 
            return 1; 
        end; 
    end; 
end; 
return nil;