读多写少

  1. 理论上,利用管程信号量可以解决所有并发问题,但JUC提供了很多工具类,_细分场景优化性能,提升易用性_
  2. 针对读多写少的并发场景,JUC提供了读写锁,即ReadWriteLock

读写锁

  1. 读写锁是一种广泛使用的通用技术,并非Java所特有
  2. 所有读写锁都遵守3条基本原则
    • 允许多个线程同时读共享变量 – 与互斥锁的重要区别
    • 只允许一个线程写共享变量
    • 如果一个写线程正常执行写操作,此时禁止读线程读取共享变量

缓存

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
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Cache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 读锁
private final Lock readLock = readWriteLock.readLock();
// 写锁
private final Lock writeLock = readWriteLock.writeLock();

// 读缓存 -- 懒加载
public V get(K key) {
V v = null;
// 读缓存
readLock.lock(); // 1
try {
v = map.get(key); // 2
} finally {
readLock.unlock(); // 3
}
// 缓存中存在,直接返回
if (v != null) { // 4
return v;
}
// 缓存中不存在,查询数据库
writeLock.lock(); // 5
try {
// 再次验证,其他线程可能已经查询过数据库了
// 避免在高并发场景下重复查询数据库的问题
v = map.get(key); // 6
if (v == null) { // 7
// 查询数据库
v = loadFromDb(key);
map.put(key, v);
}
} finally {
writeLock.unlock();
}
return v;
}

private V loadFromDb(K key) {
return null;
}

// 写缓存
public V put(K key, V value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
}

锁升级

ReadWriteLock不支持锁升级(读锁升级为写锁),readLock还没有释放,因此无法获取writeLock,这会导致_线程阻塞_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
readLock.lock();
try {
V v = map.get(key);
if (v == null) {
writeLock.lock();
try {
map.put(key, loadFromDb(key));
} finally {
writeLock.unlock();
}
}
} finally {
readLock.unlock();
}

锁降级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
readLock.lock();
if (!cacheValid) {
// 因为不允许读锁升级为写锁,先释放读锁
readLock.unlock();
writeLock.lock();
try {
if (!cacheValid) {
cacheValid = true;
}
// 释放写锁前,允许降级为读锁!!
readLock.lock(); // 1
} finally {
writeLock.unlock();
}
}

// 此时仍然持有读锁
try {
// 使用数据
} finally {
readLock.unlock();
}

小结

  1. 读写锁类似于ReentrantLock(可重入),支持公平模式和非公平模式
  2. 读锁和写锁都实现了java.util.concurrent.locks.Lock接口
  3. 但只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用newCondition,会抛出UnsupportedOperationException

参考资料

Java并发编程实战