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方法移除指定缓存项。