读写锁

1、允许多个线程同时读共享变量;

2、只允许一个线程写共享变量;

3、如果一个写线程正在执行写操作,此时禁止读线程读共享变量。

实现一个缓存

我们声明一个Cache类,然后里面用HashMap来实现存储,但是它并不是线程安全的,我们采用ReadWriteLock来保证线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Cache<K,V> {
final Map<K, V> m = new HashMap<>();
final ReadWriteLock rwl = new ReentrantReadWriteLock();
// 读锁
final Lock r = rwl.readLock();
// 写锁
final Lock w = rwl.writeLock();
// 读缓存
V get(K key) {
r.lock();
try {
return m.get(key);
}
finally {
r.unlock();
}
}
// 写缓存
V put(K key, V value) {
w.lock();
try {
return m.put(key, v);
}
finally {
w.unlock();
}
}
}

注意,在并发的场景下,写缓存时,可能会存在缓存的覆盖

实现缓存的按需加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Cache<K,V> {
final Map<K, V> m = new HashMap<>();
final ReadWriteLock rwl = new ReentrantReadWriteLock();
final Lock r = rwl.readLock();
final Lock w = rwl.writeLock();

V get(K key) {
V v = null;
// 读缓存
r.lock(); ①
try {
v = m.get(key); ②
} finally{
r.unlock(); ③
}
// 缓存中存在,返回
if (v != null) { ④
return v;
}
// 缓存中不存在,查询数据库
w.lock(); ⑤
try {
// 再次验证
// 其他线程可能已经查询过数据库
v = m.get(key); ⑥
if (v == null) { ⑦
//查询数据库
v = 省略代码无数
m.put(key, v);
}
} finally{
w.unlock();
}
return v;
}
}

读写锁的升级与降级

上面按需加载的示例代码中,在①处获取读锁,在③处释放读锁,那是否可以在②处的下面增加验证缓存并更新缓存的逻辑呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 读缓存
// 获取读锁
r.lock(); ①
try {
v = m.get(key); ②
// 没有数据,要去数据库查询
if (v == null) {
// 这里升级锁是读锁没有释放,然后加了写锁
// 升级为写锁
w.lock();
try {
// 再次验证并更新缓存
// 省略详细代码
} finally {
w.unlock();
}
}
} finally {
r.unlock(); ③
}

这样看上去好像是没有问题的,先是获取读锁,然后再升级为写锁,对此还有个专业的名字,叫锁的升级。可惜 ReadWriteLock 并不支持这种升级。在上面的代码示例中,读锁还没有释放,此时获取写锁,会导致写锁永久等待,最终导致相关线程都被阻塞,永远也没有机会被唤醒。

锁的降级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class CachedData {
Object data;
volatile boolean cacheValid;
final ReadWriteLock rwl = new ReentrantReadWriteLock();
// 读锁
final Lock r = rwl.readLock();
// 写锁
final Lock w = rwl.writeLock();

void processCachedData() {
// 获取读锁
r.lock();
if (!cacheValid) {
// 释放读锁,因为不允许读锁的升级
r.unlock();
// 获取写锁
w.lock();
try {
// 再次检查状态
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 释放写锁前,降级为读锁
// 降级是可以的
r.lock(); ①
} finally {
// 释放写锁
w.unlock();
}
}
// 此处仍然持有读锁
try {
use(data);
}
finally {
r.unlock();
}
}
}