在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache
的相关原理。java
文末提到了回收机制、移除时间通知等内容,许多朋友也挺感兴趣,此次就这两个内容再来分析分析。git
在开始以前先补习下 Java 自带的两个特性,Guava 中都有具体的应用。github
首先是 Java 中的引用。算法
在以前分享过 JVM 是根据可达性分析算法找出须要回收的对象,判断对象的存活状态都和引用
有关。设计模式
在 JDK1.2 以前这点设计的很是简单:一个对象的状态只有引用和没被引用两种区别。缓存
这样的划分对垃圾回收不是很友好,由于总有一些对象的状态处于这两之间。bash
所以 1.2 以后新增了四种状态用于更细粒度的划分引用关系:异步
A a = new A();
**这就是典型的强引用;这样的强引用关系是不能被垃圾回收的。事件回调实际上是一种常见的设计模式,好比以前讲过的 Netty 就使用了这样的设计。ide
这里采用一个 demo,试下以下功能:函数
在 Java 中利用接口来实现回调,因此须要定义一个接口:
public interface CallBackListener {
/** * 回调通知函数 * @param msg */
void callBackNotify(String msg) ;
}
复制代码
Caller 中调用 Notifier 执行提问,调用时将接口传递过去:
public class Caller {
private final static Logger LOGGER = LoggerFactory.getLogger(Caller.class);
private CallBackListener callBackListener ;
private Notifier notifier ;
private String question ;
/** * 使用 */
public void call(){
LOGGER.info("开始提问");
//新建线程,达到异步效果
new Thread(new Runnable() {
@Override
public void run() {
try {
notifier.execute(Caller.this,question);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
LOGGER.info("提问完毕,我去干其余事了");
}
//隐藏 getter/setter
}
复制代码
Notifier 收到提问,执行计算(耗时操做),最后作出响应(回调接口,告诉 Caller 结果)。
public class Notifier {
private final static Logger LOGGER = LoggerFactory.getLogger(Notifier.class);
public void execute(Caller caller, String msg) throws InterruptedException {
LOGGER.info("收到消息=【{}】", msg);
LOGGER.info("等待响应中。。。。。");
TimeUnit.SECONDS.sleep(2);
caller.getCallBackListener().callBackNotify("我在北京!");
}
}
复制代码
模拟执行:
public static void main(String[] args) {
Notifier notifier = new Notifier() ;
Caller caller = new Caller() ;
caller.setNotifier(notifier) ;
caller.setQuestion("你在哪儿!");
caller.setCallBackListener(new CallBackListener() {
@Override
public void callBackNotify(String msg) {
LOGGER.info("回复=【{}】" ,msg);
}
});
caller.call();
}
复制代码
最后执行结果:
2018-07-15 19:52:11.105 [main] INFO c.crossoverjie.guava.callback.Caller - 开始提问
2018-07-15 19:52:11.118 [main] INFO c.crossoverjie.guava.callback.Caller - 提问完毕,我去干其余事了
2018-07-15 19:52:11.117 [Thread-0] INFO c.c.guava.callback.Notifier - 收到消息=【你在哪儿!】
2018-07-15 19:52:11.121 [Thread-0] INFO c.c.guava.callback.Notifier - 等待响应中。。。。。
2018-07-15 19:52:13.124 [Thread-0] INFO com.crossoverjie.guava.callback.Main - 回复=【我在北京!】
复制代码
这样一个模拟的异步事件回调就完成了。
Guava 就是利用了上文的两个特性来实现了引用回收及移除通知。
能够在初始化缓存时利用:
来自定义键和值的引用关系。
在上文的分析中能够看出 Cache 中的 ReferenceEntry
是相似于 HashMap 的 Entry 存放数据的。
来看看 ReferenceEntry 的定义:
interface ReferenceEntry<K, V> {
/** * Returns the value reference from this entry. */
ValueReference<K, V> getValueReference();
/** * Sets the value reference for this entry. */
void setValueReference(ValueReference<K, V> valueReference);
/** * Returns the next entry in the chain. */
@Nullable
ReferenceEntry<K, V> getNext();
/** * Returns the entry's hash. */
int getHash();
/** * Returns the key for this entry. */
@Nullable
K getKey();
/* * Used by entries that use access order. Access entries are maintained in a doubly-linked list. * New entries are added at the tail of the list at write time; stale entries are expired from * the head of the list. */
/** * Returns the time that this entry was last accessed, in ns. */
long getAccessTime();
/** * Sets the entry access time in ns. */
void setAccessTime(long time);
}
复制代码
包含了不少经常使用的操做,如值引用、键引用、访问时间等。
根据 ValueReference<K, V> getValueReference();
的实现:
具备强引用和弱引用的不一样实现。
key 也是相同的道理:
当使用这样的构造方式时,弱引用的 key 和 value 都会被垃圾回收。
固然咱们也能够显式的回收:
/**
* Discards any cached value for key {@code key}.
* 单个回收
*/
void invalidate(Object key);
/**
* Discards any cached values for keys {@code keys}.
*
* @since 11.0
*/
void invalidateAll(Iterable<?> keys);
/**
* Discards all entries in the cache.
*/
void invalidateAll();
复制代码
改造了以前的例子:
loadingCache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
LOGGER.info("删除缘由={},删除 key={},删除 value={}",notification.getCause(),notification.getKey(),notification.getValue());
}
})
.build(new CacheLoader<Integer, AtomicLong>() {
@Override
public AtomicLong load(Integer key) throws Exception {
return new AtomicLong(0);
}
});
复制代码
执行结果:
2018-07-15 20:41:07.433 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:07.442 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 缓存的全部内容={1000=0}
2018-07-15 20:41:07.443 [main] INFO c.crossoverjie.guava.CacheLoaderTest - job running times=10
2018-07-15 20:41:10.461 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 删除缘由=EXPIRED,删除 key=1000,删除 value=1
2018-07-15 20:41:10.462 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 当前缓存值=0,缓存大小=1
2018-07-15 20:41:10.462 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 缓存的全部内容={1000=0}
复制代码
能够看出当缓存被删除的时候会回调咱们自定义的函数,并告知删除缘由。
那么 Guava 是如何实现的呢?
根据 LocalCache 中的 getLiveValue()
中判断缓存过时时,跟着这里的调用关系就会一直跟到:
removeValueFromChain()
中的:
enqueueNotification()
方法会将回收的缓存(包含了 key,value)以及回收缘由包装成以前定义的事件接口加入到一个本地队列中。
这样一看也没有回调咱们初始化时候的事件啊。
不过用过队列的同窗应该能猜出,既然这里写入队列,那就确定就有消费。
咱们回到获取缓存的地方:
在 finally 中执行了 postReadCleanup()
方法;其实在这里面就是对刚才的队列进行了消费:
一直跟进来就会发现这里消费了队列,将以前包装好的移除消息调用了咱们自定义的事件,这样就完成了一次事件回调。
以上全部源码:
经过分析 Guava 的源码可让咱们学习到顶级的设计及实现方式,甚至本身也能尝试编写。
Guava 里还有不少强大的加强实现,值得咱们再好好研究。
最近在总结一些 Java 相关的知识点,感兴趣的朋友能够一块儿维护。
欢迎关注公众号一块儿交流: