缓存
mybatis可以对查询的结果进行缓存,而缓存功能是由Cache及其实现类来提供的
Cache
public interface Cache {
// 缓存的唯一标志
String getId();
// 向缓存中添加缓存项
void putObject(Object key, Object value);
// 获取指定的缓存项
Object getObject(Object key);
// 移除指定的缓存项
Object removeObject(Object key);
// 清除整个缓存
void clear();
// 当前已经存储的缓存项的个数
int getSize();
// 获取读写锁,比如多个线程同时修改同一个缓存项的时候,就可以使用读写锁
default ReadWriteLock getReadWriteLock() {
return null;
}
}
众多实现类
Cache使用了装饰者的设计模式,最基本的功能由PerpetualCache来提供,其他的实现类通过装饰者的设计模式,在PerpetualCache提供功能的基础上进行增强
PerpetualCache
PerpetualCache的整体实现比较简单,主要使用一个Map来作为缓存,存储缓存项
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
接下来看几个比较常用的实现类
BlockingCache
当一个线程访问指定的key时,如果当前缓存中不存在,那么该线程会在该key上设置一个锁,接下来访问相同key的线程会阻塞在这个锁上,直到调用了putObject向缓存中添加了该key
这里进行增强的目的是为了避免缓存击穿,当多个线程同时访问缓存中的同一个key时,如果此时缓存中不存在对应key的缓存项,那么这些线程就会并发地去数据库中查询记录,从而给数据库带来了压力,通过使用这个增强,就可以确保只有一个线程去访问数据库,从而减少数据库的压力
重要属性
private final Cache delegate;
// 用来存储每个key上的锁
private final ConcurrentHashMap<Object, CountDownLatch> locks;
重要方法
getObject
public Object getObject(Object key) {
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
releaseLock(key);
}
return value;
}
private void acquireLock(Object key) {
CountDownLatch newLatch = new CountDownLatch(1);
while (true) {
// 只有当前的缓存中不存在指定的key时,才会进行put操作
CountDownLatch latch = locks.putIfAbsent(key, newLatch);
// 这里为null一共有两种情况
// (1)第一次访问缓存中的某个key
// (2)调用了releaseLock,该方法中会从locks中移除指定的key,并且唤醒其他等待线程,在这种情况下,会从多个等待线程中选出一个线程新创建的CountDownLatch作为新的锁,其他被唤醒的等待线程会在这个新的锁上进行等待,也就是说如果有多个等待线程,每个时刻只能有一个线程跳出循环,执行下面的流程,其他的线程只能等待这个线程释放锁,重新进行新一轮的竞争
if (latch == null) {
break;
}
try {
// 根据是否设置了timeout,来判断是执行有限时间等待还是无限时间等待
if (timeout > 0) {
boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException(
"Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} else {
latch.await();
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
}
}
putObject
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
releaseLock(key);
}
}
向缓存中添加缓存项,并且唤醒其他读取该缓存项的线程
private void releaseLock(Object key) {
// 刚开始看到这里的时候会有疑问,如果多个访问相同key的等待线程都被唤醒,然后并发执行,那么可能会并发地执行这个方法,从而可能会发生从locks中取出来的latch为Null,然后报错
// 后面重新看了下acquireLock那里的代码,当线程被唤醒之后,并不会立即跳出循环,执行下面的流程,而是会在多个线程中选出一个线程来跳出循环,并且让其他等待线程继续等待这个线程创建的CountDownLatch
CountDownLatch latch = locks.remove(key);
if (latch == null) {
throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
}
latch.countDown();
}
LRUCache
设定缓存项的最大个数,当缓存项的个数超过最大个数时,会将最近最少使用的缓存清退
这里主要利用的是java中的LinkedHashMap来实现的LRU
重要属性
private final Cache delegate;
// 下面的两个属性是为了实现LRU
private Map<Object, Object> keyMap;
private Object eldestKey;
重要方法
构造函数
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
public void setSize(final int size) {
// 创建一个LinkedHashMap来实现LRU,通过重写removeEldestEntry,判断当前容量是否超过设定的阈值来判断是否移除最近最少使用的key
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
putObject
public void putObject(Object key, Object value) {
// 添加缓存
delegate.putObject(key, value);
// 判断是否需要移除缓存项
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
getObject
public Object getObject(Object key) {
keyMap.get(key); // touch
return delegate.getObject(key);
}