@(MyBatis)[Cache]

MyBatis源码分析——Cache接口以及实现

Cache接口

MyBatis中的Cache以SPI实现,给需要集成其它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();
ReadWriteLock getReadWriteLock();
}

Cache实现

Cache的实现类中,Cache有不同的功能,每个功能独立,互不影响,则对于不同的Cache功能,这里使用了装饰者模式实现。

PerpetualCache

作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。

FifoCache

FIFO回收策略,装饰类,内部维护了一个队列,来保证FIFO,一旦超出指定的大小,则从队列中获取Key并从被包装的Cache中移除该键值对。

public class FifoCache implements Cache {

  // 被包装的类
private final Cache delegate;
// 队列,用来维持FIFO
private LinkedList<Object> keyList;
// 最大可容纳的大小
private int size; public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<Object>();
this.size = 1024;
} @Override
public void putObject(Object key, Object value) {
// 将Key放入队列中,并且检查一遍,如果满了则移除队列头部的元素
cycleKeyList(key);
// 执行真正的操作
delegate.putObject(key, value);
} private void cycleKeyList(Object key) {
// 将Key放入队列
keyList.addLast(key);
if (keyList.size() > size) {
// 超出指定容量,移除队列头部Key
Object oldestKey = keyList.removeFirst();
// 从缓存中移除Key对应的值
delegate.removeObject(oldestKey);
}
} // 省略部分代码
...
}

LoggingCache

日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。

public class LoggingCache implements Cache {

  private Log log;
private Cache delegate;
// 请求次数
protected int requests = 0;
// 命中次数
protected int hits = 0; public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
} @Override
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
// 命中
hits++;
}
if (log.isDebugEnabled()) {
// 如果开启了DEBUG模式,则输出命中率
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
} // 获取命中率
private double getHitRatio() {
return (double) hits / (double) requests;
} // 省略部分代码
...
}

LruCache

LRU回收策略,装饰类,在内部保存一个LinkedHashMap,用以实现LRU。

public class LruCache implements Cache {

  private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey; public LruCache(Cache delegate) {
this.delegate = delegate;
// 初始化设置LRU回收的边界容量
setSize(1024);
} public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L; // 键值移除策略,当大于指定容量时则移除最近最少使用的key/value
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
// 保存需要移除的键,因为在被包装的类中并不知道什么键需要移除
eldestKey = eldest.getKey();
}
return tooBig;
}
};
} @Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 将当前Key放到LRU的Map中,如果大于指定容量,则移除筛选的键值对
cycleKeyList(key);
} @Override
public Object getObject(Object key) {
// 让当前LRU的Map知道使用过
keyMap.get(key); //touch
return delegate.getObject(key);
} @Override
public Object removeObject(Object key) {
// 这里没有移除当前维护的key,不过在后续也会被回收,可以忽略
return delegate.removeObject(key);
} private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
// 从Cache中移除掉LRU筛选出的键值对
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
// 省略部分代码...
}

ScheduledCache

定时清空Cache,但是并没有开始一个定时任务,而是在使用Cache的时候,才去检查时间是否到了。

public class ScheduledCache implements Cache {

  private Cache delegate;
// 清除的时间间隔
protected long clearInterval;
// 上一次清除的时间
protected long lastClear; public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = 60 * 60 * 1000; // 1 hour
this.lastClear = System.currentTimeMillis();
} public void setClearInterval(long clearInterval) {
this.clearInterval = clearInterval;
} @Override
public Object getObject(Object key) {
if (clearWhenStale()) {
return null;
} else {
return delegate.getObject(key);
}
} private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
// 时间到了,清空
clear();
return true;
}
return false;
} @Override
public void clear() {
// 更新清空时间
lastClear = System.currentTimeMillis();
delegate.clear();
}
// 省略部分代码
}

SynchronizedCache

同步Cache,实现比较简单,直接使用synchronized修饰方法。

public class SynchronizedCache implements Cache {

  private Cache delegate;

  public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
// 省略部分代码
}

SoftCache

软引用回收策略,软引用只有当内存不足时才会被垃圾收集器回收。这里的实现机制中,使用了一个链表来保证一定数量的值即使内存不足也不会被回收,但是没有保存在该链表的值则有可能会被回收。

在WeakHashMap中,可以看到是将引用应用到Key的,当Key被回收后,则移除相关的Value。但是这里是将其应用到Value中,因为Key不能被回收,如果被移除的话,就会影响到整个体系,最底层的实现使用HashMap实现的,没有Key,就没有办法移除相关的值。反过来,值被回收了,将软引用对象放到队列中,可以根据Key调用removeObject移除该关联的键和软引用对象。

public class SoftCache implements Cache {
// 用于保存一定数量强引用的值
private final LinkedList<Object> hardLinksToAvoidGarbageCollection;
// 引用队列,当被垃圾收集器回收时,会将软引用对象放入此队列
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;
// 保存强引用值的数量
private int numberOfHardLinks; public SoftCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<Object>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
} @Override
public void putObject(Object key, Object value) {
// 移除被垃圾收集器回收的键值
removeGarbageCollectedItems();
// 将软件用作用到Value中
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
} @Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked")
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
// 该值被垃圾收集器回收,移除掉该项
delegate.removeObject(key);
} else {
// 这里以及下面的clear,想不通为什么要加hardLinksToAvoidGarbageCollection的同步?(在WeakCache中却没有加同步)
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
// 超出容量,则移除最先保存的引用
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
} @Override
public Object removeObject(Object key) {
// 移除被垃圾收集器回收的键值
removeGarbageCollectedItems();
return delegate.removeObject(key);
} @Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
// 这里需要清空该队列,否则即使下面调用clear,其Map清空了,但是部分值保留有引用,垃圾收集器也不会回收,会造成短暂的内存泄漏。
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
} private void removeGarbageCollectedItems() {
SoftEntry sv;
// 清空被垃圾收集器回收的value其相关联的键以及软引用
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
} // 这里软引用对象是用在value中的
private static class SoftEntry extends SoftReference<Object> {
// 保存与value相关联的Key,因为一旦被垃圾收集器回收,则此软引用对象会被放到关联的引用队列中,这样就可以根据Key,移除该键值对。
private final Object key; private SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
// 省略部分代码
}

WeakCache

弱引用回收策略,弱引用的对象一旦被垃圾收集器发现,则会被回收,无论内存是否足够。这里的实现和上面的软引用类似,除了使用WeakReference替换掉SoftReference,其它基本一样。还有一点想不通的就是,为什么SoftCache加锁了,而这里没有加锁。

  public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) {
result = weakReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
// 软引用这里加锁了
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
return result;
}

TransactionalCache

事务缓存,在提交的时候,才真正的放到Cache中,或者回滚的时候清除掉,对Cache没有影响。

public class TransactionalCache implements Cache {

  private Cache delegate;
private boolean clearOnCommit;
private Map<Object, AddEntry> entriesToAddOnCommit;
private Map<Object, RemoveEntry> entriesToRemoveOnCommit; public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, AddEntry>();
this.entriesToRemoveOnCommit = new HashMap<Object, RemoveEntry>();
} @Override
public Object getObject(Object key) {
if (clearOnCommit) return null; // issue #146
return delegate.getObject(key);
} @Override
public void putObject(Object key, Object object) {
// 移除当前事务中 待移除键值对操作
entriesToRemoveOnCommit.remove(key);
// 添加当前事务中 待增加到缓存键值对操作
entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object));
} @Override
public Object removeObject(Object key) {
// 移除增加该键值对的操作
entriesToAddOnCommit.remove(key);
// 添加移除键值对操作
entriesToRemoveOnCommit.put(key, new RemoveEntry(delegate, key));
return delegate.getObject(key);
} @Override
public void clear() {
reset();
clearOnCommit = true;
} public void commit() {
if (clearOnCommit) {
// 当提交事务时需要先清空,则清空缓存
delegate.clear();
} else {
// 应用移除键值对操作
for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
entry.commit();
}
}
// 应用添加键值对操作
for (AddEntry entry : entriesToAddOnCommit.values()) {
entry.commit();
}
reset();
} public void rollback() {
reset();
} private void reset() {
clearOnCommit = false;
entriesToRemoveOnCommit.clear();
entriesToAddOnCommit.clear();
} private static class AddEntry {
private Cache cache;
private Object key;
private Object value; public AddEntry(Cache cache, Object key, Object value) {
this.cache = cache;
this.key = key;
this.value = value;
} public void commit() {
// 提交的时候,才真正放入缓存
cache.putObject(key, value);
}
} private static class RemoveEntry {
private Cache cache;
private Object key; public RemoveEntry(Cache cache, Object key) {
this.cache = cache;
this.key = key;
} public void commit() {
// 提交的时候才真正从缓存中移除
cache.removeObject(key);
}
}
}

SerializedCache

序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。

public class SerializedCache implements Cache {
// 省略部分代码 @Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
// 先序列化后再存放到缓存中
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
} @Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
// 不为空,则反序列化,生成一份Copy
return object == null ? null : deserialize((byte[]) object);
} private byte[] serialize(Serializable value) {
try {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(value);
oos.flush();
oos.close();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
} private Serializable deserialize(byte[] value) {
Serializable result;
try {
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(value);
ObjectInputStream ois = new CustomObjectInputStream(bis);
result = (Serializable) ois.readObject();
ois.close();
} catch (Exception e) {
throw new CacheException("Error deserializing object. Cause: " + e, e);
}
return result;
} public static class CustomObjectInputStream extends ObjectInputStream { public CustomObjectInputStream(InputStream in) throws IOException {
super(in);
} @Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// 此方法只有在待序列化的类第一次序列化的时候才会被调用
// 遍历所支持的ClassLoader,加载对应的Class
return Resources.classForName(desc.getName());
}
}
}

最新文章

  1. HA简介以及HBase简介
  2. 「轉」Java的内存机制
  3. [NOIP摸你赛]Hzwer的陨石(带权并查集)
  4. App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure.-解决办法
  5. android-Service和Thread的区别
  6. 利用securecrt在linux与windows之间传输文件
  7. (转)理解OAuth 2.0
  8. CentOS+Nginx+PHP+MySQL详细配置(图解)
  9. 关于csrss.exe和winlogon.exe进程多、占用CPU高的解决办法
  10. IdentityServer4实战 - JWT Issuer 详解
  11. IP通信基础学习第三周(下)
  12. LabVIEW与Arduino的连接
  13. ssh 登录报错 packet_write_wait: Connection to x.x.x.x port 22: Broken pipe
  14. ES6基础二(数组)
  15. Oracle数据库操作总是显示运行中无法成功,删除表时报错 resource busy and acquire with NOWAIT specified
  16. 自然语言处理哪家强?【36kr】
  17. 前端-CSS-2-选择器
  18. 用AR.js实现webAR(新手入门)
  19. hdu 2197 推公式
  20. Pthreads 信号量,路障,条件变量

热门文章

  1. word 2010 建立多级结构和目录
  2. SecondaryNameNode的工作流程
  3. Linux命令随笔
  4. mysql主备(centos6.4)
  5. tomcat linux下的部署安装
  6. kmeans算法并行化的mpi程序
  7. 简单轮播js实现
  8. JQuery Ajax调用asp.net后台方法
  9. js封装的三级联动菜单(使用时只需要一行js代码)
  10. eclipse环境搭建