持续创作,加速成长!这是我参与「掘金日新计划 · 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;