BlockingCache 实现了阻塞特性,该特性是基于Java重入锁实现的。同一时刻下,BlockingCache 仅允许一个线程访问指定 key 的缓存项,其他线程将会被阻塞住。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class BlockingCache implements Cache {
private long timeout;
private final Cache delegate;
private final ConcurrentHashMap<Object, ReentrantLock> locks;

public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
}

@Override
public void putObject(Object key, Object value) {
try {
// 存储缓存项
delegate.putObject(key, value);
} finally {
// 释放锁
releaseLock(key);
}
}

@Override
public Object getObject(Object key) {
// 请求锁
acquireLock(key);
Object value = delegate.getObject(key);
// 若缓存命中,则释放锁。需要注意的是,未命中则不释放锁
if (value != null) {
// 释放锁
releaseLock(key);
}
return value;
}

@Override
public Object removeObject(Object key) {
// 释放锁
releaseLock(key);
return null;
}

private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();
// 存储 <key, Lock> 键值对到 locks 中
ReentrantLock previous = locks.putIfAbsent(key, lock);
// 如果key没有放在缓存中,则根据lock加锁
return previous == null ? lock : previous;
}

private void acquireLock(Object key) {
Lock lock = getLockForKey(key);
if (timeout > 0) {
try {
// 尝试加锁
boolean acquired = lock.tryLock(
timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException("...");
}
} catch (InterruptedException e) {
throw new CacheException("...");
}
} else {
// 加锁
lock.lock();
}
}
private void releaseLock(Object key) {
// 获取与当前 key 对应的锁
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
// 释放锁
lock.unlock();
}
}
}

在查询缓存时,getObject 方法会先获取与 key 对应的锁,并加锁。getObject 方法若返回 null,表示缓存未命中。

  • 若缓存命中,getObject 方法会释放锁
  • 否则将一直锁定。

此时MyBatis会向数据库发起查询请求,并调用putObject方法存储查询结果。然后,putObject方法会将指定key对应的锁进行解锁,这样被阻塞的线程即可恢复运行。

当指定 key 对应元素不存在于缓存中时,BlockingCache会根据lock进行加锁。此时,其他线程将会进入等待状态,直到与key对应的元素被填充到缓存中。而不是让所有线程都去访问数据库。

在上面代码中,removeObject方法的逻辑很奇怪,仅调用了releaseLock方法释放锁,

却没有调用被装饰类的removeObject方法移除指定缓存项。