网站建设 seo商情网,阿里云主机 多个网站,娃哈哈软文推广,网站开发怎么接入支付宝作者#xff1a;AmyliaY出自#xff1a;Doocs开源社区原文#xff1a;my.oschina.net/doocs/blog/4549852MyBatis 中的缓存分为一级缓存、二级缓存#xff0c;但在本质上是相同的#xff0c;它们使用的都是 Cache 接口的实现。在这篇文章里#xff0c;我们就来分析 Cache…作者AmyliaY出自Doocs开源社区原文my.oschina.net/doocs/blog/4549852MyBatis 中的缓存分为一级缓存、二级缓存但在本质上是相同的它们使用的都是 Cache 接口的实现。在这篇文章里我们就来分析 Cache 接口以及多个实现类的具体实现。1、Cache 组件MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache 包 下其中 Cache 接口 是缓存模块中最核心的接口它定义了所有缓存的基本行为。public interface Cache {/*** 获取当前缓存的 Id*/String getId();/*** 存入缓存的 key 和 valuekey 一般为 CacheKey对象*/void putObject(Object key, Object value);/*** 根据 key 获取缓存值*/Object getObject(Object key);/*** 删除指定的缓存项*/Object removeObject(Object key);/*** 清空缓存*/void clear();/*** 获取缓存的大小*/int getSize();/*** * 获取读写锁可以看到这个接口方法提供了默认的实现* 这是 Java8 的新特性只是平时开发时很少用到* */default ReadWriteLock getReadWriteLock() {return null;}
}如下图所示Cache 接口 的实现类有很多但大部分都是装饰器只有 PerpetualCache 提供了 Cache 接口 的基本实现。PerpetualCachePerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用 HashMap 记录缓存项也是通过该 HashMap 对象 的方法实现的 Cache 接口 中定义的相应方法。public class PerpetualCache implements Cache {// Cache对象 的唯一标识private final String id;// 其所有的缓存功能实现都是基于 JDK 的 HashMap 提供的方法private MapObject, Object cache new HashMap();public PerpetualCache(String id) {this.id id;}Overridepublic String getId() {return id;}Overridepublic int getSize() {return cache.size();}Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}Overridepublic Object getObject(Object key) {return cache.get(key);}Overridepublic Object removeObject(Object key) {return cache.remove(key);}Overridepublic void clear() {cache.clear();}/*** 其重写了 Object 中的 equals() 和 hashCode()方法两者都只关心 id字段*/Overridepublic 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());}Overridepublic int hashCode() {if (getId() null) {throw new CacheException(Cache instances require an ID.);}return getId().hashCode();}
}下面来看一下 cache.decorators 包 下提供的装饰器它们都直接实现了 Cache 接口扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能通过多个组合后满足一个特定的需求。BlockingCacheBlockingCache 是阻塞版本的缓存装饰器它会保证只有一个线程到数据库中查找指定 key 对应的数据。public class BlockingCache implements Cache {// 阻塞超时时长private long timeout;// 持有的被装饰者private final Cache delegate;// 每个 key 都有其对应的 ReentrantLock锁对象private final ConcurrentHashMapObject, ReentrantLock locks;// 初始化 持有的持有的被装饰者 和 锁集合public BlockingCache(Cache delegate) {this.delegate delegate;this.locks new ConcurrentHashMap();}
}假设 线程 A 在 BlockingCache 中未查找到 keyA 对应的缓存项时线程 A 会获取 keyA 对应的锁这样线程 A 在后续查找 keyA 时其它线程会被阻塞。// 根据 key 获取锁对象然后上锁private void acquireLock(Object key) {// 获取 key 对应的锁对象Lock lock getLockForKey(key);// 获取锁带超时时长if (timeout 0) {try {boolean acquired lock.tryLock(timeout, TimeUnit.MILLISECONDS);if (!acquired) { // 超时则抛出异常throw new CacheException(Couldnt get a lock in timeout for the key key at the cache delegate.getId());}} catch (InterruptedException e) {// 如果获取锁失败则阻塞一段时间throw new CacheException(Got interrupted while trying to acquire lock for key key, e);}} else {// 上锁lock.lock();}}private ReentrantLock getLockForKey(Object key) {// Java8 新特性Map系列类 中新增的方法// V computeIfAbsent(K key, Function? super K, ? extends V mappingFunction)// 表示若 key 对应的 value 为空则将第二个参数的返回值存入该 Map集合 并返回return locks.computeIfAbsent(key, k - new ReentrantLock());}假设 线程 A 从数据库中查找到 keyA 对应的结果对象后将结果对象放入到 BlockingCache 中此时 线程 A 会释放 keyA 对应的锁唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据而不是再次访问数据库。 Overridepublic void putObject(Object key, Object value) {try {// 存入 key 和其对应的缓存项delegate.putObject(key, value);} finally {// 最后释放锁releaseLock(key);}}private void releaseLock(Object key) {ReentrantLock lock locks.get(key);// 锁是否被当前线程持有if (lock.isHeldByCurrentThread()) {// 是则释放锁lock.unlock();}}FifoCache 和 LruCache在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器当向缓存添加数据时如果缓存项的个数已经达到上限则会将缓存中最老即最早进入缓存的缓存项删除。public class FifoCache implements Cache {// 被装饰对象private final Cache delegate;// 用一个 FIFO 的队列记录 key 的顺序其具体实现为 LinkedListprivate final DequeObject keyList;// 决定了缓存的容量上限private int size;// 国际惯例通过构造方法初始化自己的属性缓存容量上限默认为 1024个public FifoCache(Cache delegate) {this.delegate delegate;this.keyList new LinkedList();this.size 1024;}Overridepublic String getId() {return delegate.getId();}Overridepublic int getSize() {return delegate.getSize();}public void setSize(int size) {this.size size;}Overridepublic void putObject(Object key, Object value) {// 存储缓存项之前先在 keyList 中注册cycleKeyList(key);// 存储缓存项delegate.putObject(key, value);}private void cycleKeyList(Object key) {// 在 keyList队列 中注册要添加的 keykeyList.addLast(key);// 如果注册这个 key 会超出容积上限则把最老的一个缓存项清除掉if (keyList.size() size) {Object oldestKey keyList.removeFirst();delegate.removeObject(oldestKey);}}Overridepublic Object getObject(Object key) {return delegate.getObject(key);}Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}// 除了清理缓存项还要清理 key 的注册列表Overridepublic void clear() {delegate.clear();keyList.clear();}
}LruCache 是按照近期最少使用算法Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。public class LruCache implements Cache {// 被装饰者private final Cache delegate;// 这里使用的是 LinkedHashMap它继承了 HashMap但它的元素是有序的private MapObject, Object keyMap;// 最近最少被使用的缓存项的 keyprivate Object eldestKey;// 国际惯例构造方法中进行属性初始化public LruCache(Cache delegate) {this.delegate delegate;// 这里初始化了 keyMap并定义了 eldestKey 的取值规则setSize(1024);}public void setSize(final int size) {// 初始化 keyMap同时指定该 Map 的初始容积及加载因子第三个参数true 表示 该LinkedHashMap// 记录的顺序是 accessOrder即LinkedHashMap.get()方法 会改变其中元素的顺序keyMap new LinkedHashMapObject, Object(size, .75F, true) {private static final long serialVersionUID 4267176411845948333L;// 当调用 LinkedHashMap.put()方法 时该方法会被调用Overrideprotected boolean removeEldestEntry(Map.EntryObject, Object eldest) {boolean tooBig size() size;if (tooBig) {// 当已达到缓存上限更新 eldestKey字段后面将其删除eldestKey eldest.getKey();}return tooBig;}};}// 存储缓存项Overridepublic void putObject(Object key, Object value) {delegate.putObject(key, value);// 记录缓存项的 key超出容量则清除最久未使用的缓存项cycleKeyList(key);}private void cycleKeyList(Object key) {keyMap.put(key, key);// eldestKey 不为空则表示已经达到缓存上限if (eldestKey ! null) {// 清除最久未使用的缓存delegate.removeObject(eldestKey);// 制空eldestKey null;}}Overridepublic Object getObject(Object key) {// 访问 key元素 会改变该元素在 LinkedHashMap 中的顺序keyMap.get(key); //touchreturn delegate.getObject(key);}Overridepublic String getId() {return delegate.getId();}Overridepublic int getSize() {return delegate.getSize();}Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}Overridepublic void clear() {delegate.clear();keyMap.clear();}
}SoftCache 和 WeakCache在分析 SoftCache 和 WeakCache 实现之前我们再温习一下 Java 提供的 4 种引用类型强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference 和虚引用 PhantomReference。•强引用 平时用的最多的如 Object obj new Object()新建的 Object 对象 就是被强引用的。如果一个对象被强引用即使是 JVM 内存空间不足要抛出 OutOfMemoryError 异常GC 也绝不会回收该对象。•软引用 仅次于强引用的一种引用它使用类 SoftReference 来表示。当 JVM 内存不足时GC 会回收那些只被软引用指向的对象从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象例如 数据库缓存中的对象就可以从数据库中恢复所以软引用可以用来实现缓存下面要介绍的 SoftCache 就是通过软引用实现的。另外由于在程序使用软引用之前的某个时刻其所指向的对象可能己经被 GC 回收掉了所以通过 Reference.get()方法 来获取软引用所指向的对象时总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。•弱引用 弱引用使用 WeakReference 表示它不会阻止所引用的对象被 GC 回收。在 JVM 进行垃圾回收时如果指向一个对象的所有引用都是弱引用那么该对象会被回收。所以只被弱引用所指向的对象其生存周期是 两次 GC 之间 的这段时间而只被软引用所指向的对象可以经历多次 GC直到出现内存紧张的情况才被回收。•虚引用 最弱的一种引用类型由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制但很少使用。•引用队列ReferenceQueue ) 很多场景下我们的程序需要在一个对象被 GC 时得到通知引用队列就是用于收集这些信息的队列。在创建 SoftReference 对象 时可以为其关联一个引用队列当 SoftReference 所引用的对象被 GC 时 JVM 就会将该 SoftReference 对象 添加到与之关联的引用队列中。当需要检测这些通知信息时就可以从引用队列中获取这些 SoftReference 对象。不仅是 SoftReference弱引用和虚引用都可以关联相应的队列。现在来看一下 SoftCache 的具体实现。public class SoftCache implements Cache {// 这里使用了 LinkedList 作为容器在 SoftCache 中最近使用的一部分缓存项不会被 GC// 这是通过将其 value 添加到 hardLinksToAvoidGarbageCollection集合 实现的即有强引用指向其valueprivate final DequeObject hardLinksToAvoidGarbageCollection;// 引用队列用于记录已经被 GC 的缓存项所对应的 SoftEntry对象private final ReferenceQueueObject queueOfGarbageCollectedEntries;// 持有的被装饰者private final Cache delegate;// 强连接的个数默认为 256private int numberOfHardLinks;// 构造方法进行属性的初始化public SoftCache(Cache delegate) {this.delegate delegate;this.numberOfHardLinks 256;this.hardLinksToAvoidGarbageCollection new LinkedList();this.queueOfGarbageCollectedEntries new ReferenceQueue();}private static class SoftEntry extends SoftReferenceObject {private final Object key;SoftEntry(Object key, Object value, ReferenceQueueObject garbageCollectionQueue) {// 指向 value 的引用是软引用并且关联了 引用队列super(value, garbageCollectionQueue);// 强引用this.key key;}}Overridepublic void putObject(Object key, Object value) {// 清除已经被 GC 的缓存项removeGarbageCollectedItems();// 添加缓存delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));}private void removeGarbageCollectedItems() {SoftEntry sv;// 遍历 queueOfGarbageCollectedEntries集合清除已经被 GC 的缓存项 valuewhile ((sv (SoftEntry) queueOfGarbageCollectedEntries.poll()) ! null) {delegate.removeObject(sv.key);}}Overridepublic Object getObject(Object key) {Object result null;SuppressWarnings(unchecked) // assumed delegate cache is totally managed by this cache// 用一个软引用指向 key 对应的缓存项SoftReferenceObject softReference (SoftReferenceObject) delegate.getObject(key);// 检测缓存中是否有对应的缓存项if (softReference ! null) {// 获取 softReference 引用的 valueresult softReference.get();// 如果 softReference 引用的对象已经被 GC则从缓存中清除对应的缓存项if (result null) {delegate.removeObject(key);} else {synchronized (hardLinksToAvoidGarbageCollection) {// 将缓存项的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存hardLinksToAvoidGarbageCollection.addFirst(result);// 如果 hardLinksToAvoidGarbageCollection 的容积已经超过 numberOfHardLinks// 则将最老的缓存项从 hardLinksToAvoidGarbageCollection 中清除FIFOif (hardLinksToAvoidGarbageCollection.size() numberOfHardLinks) {hardLinksToAvoidGarbageCollection.removeLast();}}}}return result;}Overridepublic Object removeObject(Object key) {// 清除指定的缓存项之前也会先清理被 GC 的缓存项removeGarbageCollectedItems();return delegate.removeObject(key);}Overridepublic void clear() {synchronized (hardLinksToAvoidGarbageCollection) {// 清理强引用集合hardLinksToAvoidGarbageCollection.clear();}// 清理被 GC 的缓存项removeGarbageCollectedItems();// 清理最底层的缓存项delegate.clear();}Overridepublic String getId() {return delegate.getId();}Overridepublic int getSize() {removeGarbageCollectedItems();return delegate.getSize();}public void setSize(int size) {this.numberOfHardLinks size;}
}WeakCache 的实现与 SoftCache 基本类似唯一的区别在于其中使用 WeakEntry继承了 WeakReference封装真正的 value 对象其他实现完全一样。另外还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器它的 clearInterval 字段 记录了两次缓存清理之间的时间间隔默认是一小时lastClear 字段 记录了最近一次清理的时间戳。ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法在执行时都会根据这两个字段检测是否需要进行清理操作清理操作会清空缓存中所有缓存项。LoggingCache 在 Cache 的基础上提供了日志功能它通过 hit 字段 和 request 字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中会统计命中次数和访问次数 这两个指标井按照指定的日志输出方式输出命中率。SynchronizedCache 通过在每个方法上添加 synchronized 关键字为 Cache 添加了同步功能有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。SerializedCache 提供了将 value 对象 序列化的功能。SerializedCache 在添加缓存项时会将 value 对应的 Java 对象 进行序列化井将序列化后的 byte[]数组 作为 value 存入缓存 。SerializedCache 在获取缓存项时会将缓存项中的 byte[]数组 反序列化成 Java 对象。不使用 SerializedCache 装饰器 进行装饰的话每次从缓存中获取同一 key 对应的对象时得到的都是同一对象任意一个线程修改该对象都会影响到其他线程以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时都会通过反序列化得到一个全新的对象。SerializedCache 使用的序列化方式是 Java 原生序列化。2、CacheKey在 Cache 中唯一确定一个缓存项需要使用缓存项的 key 进行比较MyBatis 中因为涉及 动态 SQL 等多方面因素 其缓存项的 key 不能仅仅通过一个 String 表示所以 MyBatis 提供了 CacheKey 类 来表示缓存项的 key在一个 CacheKey 对象 中可以封装多个影响缓存项的因素。CacheKey 中可以添加多个对象由这些对象共同确定两个 CacheKey 对象 是否相同。public class CacheKey implements Cloneable, Serializable {private static final long serialVersionUID 1146682552656046210L;public static final CacheKey NULL_CACHE_KEY new NullCacheKey();private static final int DEFAULT_MULTIPLYER 37;private static final int DEFAULT_HASHCODE 17;// 参与计算hashcode默认值DEFAULT_MULTIPLYER 37private final int multiplier;// 当前CacheKey对象的hashcode默认值DEFAULT_HASHCODE 17private int hashcode;// 校验和private long checksum;private int count;// 由该集合中的所有元素 共同决定两个CacheKey对象是否相同一般会使用一下四个元素// MappedStatement的id、查询结果集的范围参数RowBounds的offset和limit// SQL语句其中可能包含占位符?、SQL语句中占位符的实际参数private ListObject updateList;// 构造方法初始化属性public CacheKey() {this.hashcode DEFAULT_HASHCODE;this.multiplier DEFAULT_MULTIPLYER;this.count 0;this.updateList new ArrayList();}public CacheKey(Object[] objects) {this();updateAll(objects);}public void update(Object object) {int baseHashCode object null ? 1 : ArrayUtil.hashCode(object);// 重新计算count、checksum和hashcode的值count;checksum baseHashCode;baseHashCode * count;hashcode multiplier * hashcode baseHashCode;// 将object添加到updateList集合updateList.add(object);}public int getUpdateCount() {return updateList.size();}public void updateAll(Object[] objects) {for (Object o : objects) {update(o);}}/*** CacheKey重写了 equals() 和 hashCode()方法这两个方法使用上面介绍* 的 count、checksum、hashcode、updateList 比较两个 CacheKey对象 是否相同*/Overridepublic boolean equals(Object object) {// 如果为同一对象直接返回 trueif (this object) {return true;}// 如果 object 都不是 CacheKey类型直接返回 falseif (!(object instanceof CacheKey)) {return false;}// 类型转换一下final CacheKey cacheKey (CacheKey) object;// 依次比较 hashcode、checksum、count如果不等直接返回 falseif (hashcode ! cacheKey.hashcode) {return false;}if (checksum ! cacheKey.checksum) {return false;}if (count ! cacheKey.count) {return false;}// 比较 updateList 中的元素是否相同不同直接返回 falsefor (int i 0; i updateList.size(); i) {Object thisObject updateList.get(i);Object thatObject cacheKey.updateList.get(i);if (!ArrayUtil.equals(thisObject, thatObject)) {return false;}}return true;}Overridepublic int hashCode() {return hashcode;}Overridepublic String toString() {StringJoiner returnValue new StringJoiner(:);returnValue.add(String.valueOf(hashcode));returnValue.add(String.valueOf(checksum));updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);return returnValue.toString();}Overridepublic CacheKey clone() throws CloneNotSupportedException {CacheKey clonedCacheKey (CacheKey) super.clone();clonedCacheKey.updateList new ArrayList(updateList);return clonedCacheKey;}
}