二级缓存装饰器
PerpetualCache 实现了 Cache 接口,提供了缓存的基本功能,为了 PerpetualCache 添加其他功能,采用了装饰器模式,添加了特定的功能。
CacheBuilder
mapper 实体二级缓存构造器,在这里对 PerpetualCache 进行了添加了装饰器,装饰了不同的功能。
public class CacheBuilder {
// Cache 对象的唯一标识, 一般情况下对应映射文件中的配置 namespace
private final String id;
// Cache 口的真正实现类,默认位是前面介绍的 PerpetualCache
private Class<? extends Cache> implementation;
// 装饰器集合,默认只包含 LruCache class;淘汰策略的装饰器集合
private final List<Class<? extends Cache>> decorators;
// Cache 大小
private Integer size;
// 清理周期
private Long clearInterval;
// 是否可读写
private boolean readWrite;
// 其他配置信息
//<cache type="com.domain.something.MyCustomCache">
// <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
//</cache>
private Properties properties;
// 是否阻塞
private boolean blocking;
public CacheBuilder(String id) {
this.id = id;
this.decorators = new ArrayList<>();
}
public CacheBuilder implementation(Class<? extends Cache> implementation) {
this.implementation = implementation;
return this;
}
public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
if (decorator != null) {
this.decorators.add(decorator);
}
return this;
}
public CacheBuilder size(Integer size) {
this.size = size;
return this;
}
public CacheBuilder clearInterval(Long clearInterval) {
this.clearInterval = clearInterval;
return this;
}
public CacheBuilder readWrite(boolean readWrite) {
this.readWrite = readWrite;
return this;
}
public CacheBuilder blocking(boolean blocking) {
this.blocking = blocking;
return this;
}
public CacheBuilder properties(Properties properties) {
this.properties = properties;
return this;
}
public Cache build() {
// 如采implementation 字段和 decorators 集合为空,则为其设立默认佳, implementation 默认
// 值是 PerpetualCache class, decorators 集合,默认只包含 LruCache.cla ss
setDefaultImplementations();
// 根据 implementation 指定的类型 通过反射获取参数为 String 类型的构造方法,并通过该构造方法创建 Cache 对象
Cache cache = newBaseCacheInstance(implementation, id);
// 根据<cache>节点 <property>信息,初始化 Cache 对象
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
// 检测 cache 对象 类型,如果是 PerpetualCache 类型,则为其添加 decorators 集合中的装饰器;
// 如果自定义类型的 Cache 接口实现,则不添加 decorators 集合中的装饰器
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
// 通过反射获取参数为 Cache 类型的构造方法,并通过该构造方法创建装饰器
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache); // 配置 cache 对象的属性
}
//添加 MyBatis 中提供的标准装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 如果不是 LoggingCache 的子类,则添加 LoggingCache 装饰器
cache = new LoggingCache(cache);
}
return cache;
}
/**
* 设置默认缓存类和淘汰策略
*/
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
/**
* 添加 MyBatis 中提供的标准装饰器;
* 根据 cache 不同配置属性,添加不同类型的缓存装饰器
* @param cache
* @return
*/
private Cache setStandardDecorators(Cache cache) {
try {
// 创建 cache 对象对应的 MetaObject 对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
cache = new SerializedCache(cache);
}
// 默认添加 LoggingCache 和 SynchronizedCache 两个装饰器
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
/**
* 根据 <cache> 节点下配置的 <property> 的信息,初始化 Cache 对象
* @param cache
*/
private void setCacheProperties(Cache cache) {
if (properties != null) {
// cache 对应的创建 MetaObject 对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
// 配置项的名称, Cache 对应的属性名称
String name = (String) entry.getKey();
// 配置项的值, Cache 对应的属性值
String value = (String) entry.getValue();
// 检测 cache 是否有该属性对应的 setter 方法
if (metaCache.hasSetter(name)) {
// 获取该属性的类型
Class<?> type = metaCache.getSetterType(name);
// 进行类 转换 ,并设置该属性位
if (String.class == type) {
metaCache.setValue(name, value);
} else if (int.class == type
|| Integer.class == type) {
metaCache.setValue(name, Integer.valueOf(value));
} else if (long.class == type
|| Long.class == type) {
metaCache.setValue(name, Long.valueOf(value));
} else if (short.class == type
|| Short.class == type) {
metaCache.setValue(name, Short.valueOf(value));
} else if (byte.class == type
|| Byte.class == type) {
metaCache.setValue(name, Byte.valueOf(value));
} else if (float.class == type
|| Float.class == type) {
metaCache.setValue(name, Float.valueOf(value));
} else if (boolean.class == type
|| Boolean.class == type) {
metaCache.setValue(name, Boolean.valueOf(value));
} else if (double.class == type
|| Double.class == type) {
metaCache.setValue(name, Double.valueOf(value));
} else {
throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
}
}
}
}
// 如果 Cache 类继承了 InitializingObject 接口, 则调用其 initialize() 方法继续 定义的初始化操作
if (InitializingObject.class.isAssignableFrom(cache.getClass())) {
try {
((InitializingObject) cache).initialize();
} catch (Exception e) {
throw new CacheException("Failed cache initialization for '"
+ cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
}
}
}
private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
try {
return cacheConstructor.newInstance(id);
} catch (Exception e) {
throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
}
}
private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
try {
return cacheClass.getConstructor(String.class);
} catch (Exception e) {
throw new CacheException("Invalid base cache implementation (" + cacheClass + "). "
+ "Base cache implementations must have a constructor that takes a String id as a parameter. Cause: " + e, e);
}
}
private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
try {
return cacheConstructor.newInstance(base);
} catch (Exception e) {
throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
}
}
private Constructor<? extends Cache> getCacheDecoratorConstructor(Class<? extends Cache> cacheClass) {
try {
return cacheClass.getConstructor(Cache.class);
} catch (Exception e) {
throw new CacheException("Invalid cache decorator (" + cacheClass + "). "
+ "Cache decorators must have a constructor that takes a Cache instance as a parameter. Cause: " + e, e);
}
}
}
BlockingCache
简单的、低效版本的阻塞缓存个装饰器,阻塞是指只有一个线程取从缓存中查询数据,如果缓存没有,只有一个线程可以从数据库中查询数据。
一个 key 一个 ReentrantLock 锁,只在 getObject() 方法上加锁,不在 putObject() 方法上加锁,但是锁的释放有两种情况:
第一种是: 从缓存中查找到 key 的数据,释放锁;
第二种是:从缓存中查找到的数据为 Null,并不会释放锁,然后这个线程还会从数据库中 查询数据,查询到数据之后,放入缓存,才释放锁;这样只允许一个线程从数据库中查询数据,其他线程是不能从数据库中查询数据。
这样的设计非常好,锁的获取是在查询数据的时候,包括查询缓存和数据库,而锁的释放是已数据是否查到为基础,可以在缓存中查到释放锁,也可以在缓存内没有,从数据库查到释放锁,一个锁,针对两次锁竞争,一是在两个线程查询缓存的时候,二是在两个线程查询数据库的时候,一旦一个线程获取了锁,只有获得了数据才会释放锁;而且不用在查询数据库的时候,再去加锁,一锁两用。
一般的锁,是在写数据的时候,获取锁,然后可以多个线程去读数据,而这个锁,是在读数据的时候就获取锁,然后读到数据就释放锁,如果读不到数据,就一直加锁,直到从数据库中,查询出数据放入缓存中,才会释放锁。这有点意想不到,很好的设计。
第一中释放锁

第二种释放锁

public class BlockingCache implements Cache {
// 阻塞超时时间
private long timeout;
// 被装饰的底层 cache 对象
private final Cache delegate;
// 一个对象一个锁,ConcurrentHashMap 是线程安全的
// ReentrantLock 可重入锁
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@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) {
// despite of its name, this method is called only to release locks
// 释放锁
releaseLock(key);
return null;
}
/**
* 清空缓存
*/
@Override
public void clear() {
delegate.clear();
}
/**
* 根据 key,新建一个对象的锁,或者得到对象的锁
* @param key
* @return
*/
private ReentrantLock getLockForKey(Object key) {
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
/**
* 上锁
* @param key
*/
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("Couldn't 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();
}
}
/**
* 释放锁
* @param key
*/
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
// 当前线程是否,继续持有锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
SoftCache & WeakCache
Java 提供了四种引用
- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 幽灵引用(Phantom Reference)
强引用
强引用是 Java 编程中最普遍的引用,例如 Object obj = new Object()中,新建的 Object 对象就是被强引用的。如果一个对象被强引用,即使是 Java 虚拟机内存空间不足时,GC(垃圾收集器)也绝不会回收该对象。当 Java 虚拟机内存不足时,就可能导致内存溢出,我们常见的就是 OutOfMemoryError 异常。
软引用
软引用是引用强度仅弱于强引用的一种引用,它使用类 SofReference 来表示。当 Java虚拟机内存不足时, GC 会回收那些只被软引用指向的对象,从而避免内存溢出。在GC 释放了那些只被软引用指向的对象之后,虚拟机内存依然不是,才会抛出OutOtMemoryError 异常。软引用适合引用那些可以通过其他方式恢复的对象,例如,数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面将要介绍的 SofCache 就是通过软引用实现的。另外,由于在程序使用软引用之前的某个时刻 ,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get() 方法来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null ,来判断被软引用的对象是否还活。
引用队列( ReferenceQueue )
用于存储被弱引用,引用的对象的一个队列,当被弱引用的对象被 GC 时,就会将它添加到引用队列中。
在很多场景下,我们的程序需要在一个对象的可达性(是否已经被 GC 回收)发生变化时得到通知,引用队列就是用于收集这些信息的队列。在创建 SofReference 对象时,可以为其关联一个引用队列,当 SofReference 所引用的对象被 GC 回收时, Java 虚拟机就会将该 SoftReference 对象添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference 对象。不仅是 SofReference ,下面介绍的弱引用和幽灵引用都可以关联相应的队列。可以参考 java.util. WeakHashMap 的代码,其中应用了弱引用和引用队列的相关知识,下面会介绍该类的实现原理。
弱引用
弱引用的强度比软引用的强度还要弱。弱引用使用 WeakReference 表示,它可以引一个对象,但并不阻止被引用的对象被 GC 回收。在 JVM 虚拟机进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。由此可见,只被弱引用所指向的对象的生存周期是两次 GC 之间的这段时间,而只被软引用所指向的对象可以经历多次 GC ,直到出现内存紧张的情况才被回收。
弱引用典型的应用情景是就是 JDK 提供的java.util.WeakHashMap WeakHashMap.Entry 实现继承了 WeakReference, Entry 弱引用 key ,强引用 value ,如图 2-47 所示。

当不在由强引用指向 key 时,则 key 可以被垃圾回收,当 key 被垃圾回收之后,对应的 Entry 对象会被 Java 虚拟机加入到其关联的队列中。当应用程序下次操作 WeakHashMap 时,例如对 WeakHashMap 的扩容操作,就会遍历关联的引用队列,将其中的 Entry 对象从 WeakHashMap 中删除。
幽灵引用
在介绍幽灵引用之前,要先了解一下 Java 提供的对象终止化机制。在 Object 类里面有个 finalize() 方法,设计该方法的初中是在一个对象被真正回收之前,执行一些清理工作,但由于 GC 的运行时间是不固定的,所以在这些清理工作的实际时间也是无法预知的,而且 JVM 虚拟机不能保证 finalize() 方法一定会被调用。另外,使用 finalize() 方法还会导致严重的内存消耗和性能损失。由于 finalize() 方法的种种问题,该方法已经被废弃,而我们可以使用幽灵引用实现替代方案。
幽灵引用,他是最弱的一种引用。有类 PhantomReference 表示。
在引用的对象未被 GC 回收时,调用前面介绍的 SoftReference 以及 WeakReference 的 get() 方法,得到的是引用的对象;当引用的对象已经被 GC 回收时,则得到 null;但 PhantomReference.get() 方法始终返回 null。
在创建幽灵引用的时候必须要指定一个引用队列。当 GC 准备回收一个对象时,如果发现它还有幽灵引用,就会在回收对象的内存之前,把该虚引用加入到与之关联的引用队列中。程序可以通过检查该引用队列里面的内容,跟踪对象是否已经被回收并进行了清理工作。幽灵引用还可以用来实现比较精细的内存使用控制,例如应用程序可以在确定一个对象要被回收之后,再申请内存创建新对象。
引用垃圾垃圾回收方案

SoftCache & WeakCache
SoftCache 是根据对象软引用,对象的生命周期,是否被 GC 了,来确实是否缓存这个对象,从而实现了 SoftCache。
WeakCache 和 SoftCache 原理一样。
public class SoftCache implements Cache {
// 在 SoftCache 中,最近使用的一部分缓存不会被 GC 回收,这就是通过将其 value 添加到 hardLinksToAvoidGarbageCollection 集合中实现的(即有强引用指向其 value),
// 是一个 LinkedList 集合
private final Deque<Object> hardLinksToAvoidGarbageCollection;
// ReferenceQueue 引用队列,用于记录已经被 GC 回收的缓存项所对应的 SoftEntry
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
// 底层 cache 对象,缓存对象 key 是 key, value 是 new SoftEntry(key, value, queueOfGarbageCollectedEntries) 对象
private final Cache delegate;
// 强连接个数,默认值是 256
private int numberOfHardLinks;
public SoftCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
// 清除已经被 GC 回收的缓存项
removeGarbageCollectedItems();
// key 是强引用,value 是弱引用,value 关联了 引用队列
// 添加缓存项
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
/**
* 从缓存中查找对应的 value,处理被 GC 回收的 value 对应的缓存项,还会更新 hardLinksToAvoidGarbageCollection 集合
* @param key The key
* @return
*/
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
// 查询缓存项
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) { // 检测缓存中是否有对应的缓存项
result = softReference.get(); // 获取 SoftReference 引用的 value
if (result == null) { // 已经被回收
delegate.removeObject(key); // 从缓存中清除对应的缓存项
} else { //未被 GC 回收
// See #586 (and #335) modifications need more than a read lock
synchronized (hardLinksToAvoidGarbageCollection) {
// 将缓存项的 value 添加到 hardLinksToAvoidGarbageCollection 集合
hardLinksToAvoidGarbageCollection.addFirst(result);
// 超过 numberOfHardLinks,则将最老的缓存项清除,先清除老的缓存项,先进先出
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) {
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
/**
* 清除已经被 GC 回收的缓存项
*/
private void removeGarbageCollectedItems() {
SoftEntry sv;
// 遍历 queueOfGarbageCollectedEntries 集合
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
// 将已经被 GC 回收的 value 对象对应的缓存项清除
delegate.removeObject(sv.key);
}
}
/**
* SoftCache 中缓存项的 value 是 SoftEntry 对象,SoftEntry 继承了 SoftReference,其中指向 key 的引用是强引用,而指向 value 的引用是软引用。
*/
private static class SoftEntry extends SoftReference<Object> {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
// 指向 value 的引用是软引用,且关联了引用队列
super(value, garbageCollectionQueue);
this.key = key; //强引用
}
}
}
ScheduledCache&LoggingCache&Synchronized&CacheSerializedCache
- ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval 宇段记录了两次缓存清理之间的时间间隔,默认是一小时, lastClear 字段记录了最近一次清理的时间戳。 ScheduledCache.getObject()、putObject()、 removeObject() 等核心方法在执行时,都会根据这两个字段检测是否需要进行清理操作 ,清理操作会清空缓存中所有缓存项。
- LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 宇段和 request 字段记录了 Cache 的命中次数和访问次数。在LoggingCache.getObject() 方法中会统计命中次数和访问次数这两个指标,井按照指定的日志输出方式输出命中率。
- SynchronizedCache 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能,有点类似于 JDK Collections 中的 SynchronizedCollection 内部类的实现 SynchronizedCache 。
- SerializedCache 提供了将 value 对象序列化的功能。 SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,井将序列化后的 byte[] 数组作为 value 存入缓存SerializedCache 在获取缓存项时,会将缓存项中 byte[] 数组反序列化成 Java 对象。使用前面介绍的 Cache 装饰器实现进行装饰之后,每次从缓存中获取同 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影 响到其他线程以及缓存中的对象;而
SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。
SerializedCache 使用的序列化方式是 Java 原生序列化。
网友评论