Guava 源码分析(Cache 原理 对象引用、事件回调)
前言
在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache
的相关原理。
文末提到了回收机制、移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析。
在开始之前先补习下 Java 自带的两个特性,Guava 中都有具体的应用。
Java 中的引用
首先是 Java 中的引用。
在之前分享过 JVM 是根据可达性分析算法找出需要回收的对象,判断对象的存活状态都和引用
有关。
在 JDK1.2 之前这点设计的非常简单:一个对象的状态只有引用和没被引用两种区别。
这样的划分对垃圾回收不是很友好,因为总有一些对象的状态处于这两之间。
因此 1.2 之后新增了四种状态用于更细粒度的划分引用关系:
- 强引用(Strong Reference):这种对象最为常见,比如
A a = new A();
这就是典型的强引用;这样的强引用关系是不能被垃圾回收的。 - 软引用(Soft Reference):这样的引用表明一些有用但不是必要的对象,在将发生垃圾回收之前是需要将这样的对象再次回收。
- 弱引用(Weak Reference):这是一种比软引用还弱的引用关系,也是存放非必须的对象。当垃圾回收时,无论当前内存是否足够,这样的对象都会被回收。
- 虚引用(Phantom Reference):这是一种最弱的引用关系,甚至没法通过引用来获取对象,它唯一的作用就是在被回收时可以获得通知。
事件回调
事件回调其实是一种常见的设计模式,比如之前讲过的 Netty 就使用了这样的设计。
这里采用一个 demo,试下如下功能:
- Caller 向 Notifier 提问。
- 提问方式是异步,接着做其他事情。
- Notifier 收到问题执行计算然后回调 Caller 告知结果。
在 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 的用法
Guava 就是利用了上文的两个特性来实现了引用回收及移除通知。
引用
可以在初始化缓存时利用:
- CacheBuilder.weakKeys()
- CacheBuilder.weakValues()
- CacheBuilder.softValues()
来自定义键和值的引用关系。
在上文的分析中可以看出 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 相关的知识点,感兴趣的朋友可以一起维护。
欢迎关注公众号一起交流:
Guava 源码分析(Cache 原理 对象引用、事件回调)的更多相关文章
- Guava 源码分析之Cache的实现原理
Guava 源码分析之Cache的实现原理 前言 Google 出的 Guava 是 Java 核心增强的库,应用非常广泛. 我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Goog ...
- jQuery 源码分析(十八) ready事件详解
ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加 ...
- MyBatis 源码分析 - 缓存原理
1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...
- Spring Boot 揭秘与实战 源码分析 - 工作原理剖析
文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...
- Tomcat源码分析——请求原理分析(下)
前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...
- Tomcat源码分析——请求原理分析(中)
前言 在<TOMCAT源码分析——请求原理分析(上)>一文中已经介绍了关于Tomcat7.0处理请求前作的初始化和准备工作,请读者在阅读本文前确保掌握<TOMCAT源码分析——请求原 ...
- Tomcat源码分析——请求原理分析(上)
前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...
- wifidog源码分析 - wifidog原理 tiger
转:http://www.cnblogs.com/tolimit/p/4223644.html wifidog源码分析 - wifidog原理 wifidog是一个用于配合认证服务器实现无线网页认证功 ...
- vscode源码分析【五】事件分发机制
第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...
随机推荐
- VS2017打包注册IE插件及修改IE安全选项设置
前言 最近项目需要在浏览器环境下读取员工身份证信息,要实现网页与硬件设备通信,考虑了几种实现方式: 1.借助ActiveX插件,通过程序库直接与设备通信. 优点:厂家提供了IE插件,开发简单 缺点:只 ...
- 二维条码扫描模组在肯德基KFC的无纸化点餐解决方案
在如今提倡节约资源的环境下,肯德基在品牌发展中,逐渐实现无纸化点餐,不仅节约了纸质点餐单,而且还具有节约资源的示范作用.而其中二维码扫描模组是这套无纸化点餐方案的重点,在整套设备中,加入二维码扫描模组 ...
- 我的BO之强类型
弱类型的缺点 有些程序员对类型比较随意,从前端传来的数据,不管应该是什么类型,都以String接收.然后在什么地方转成应该有的类型则要"看心情",在Controller, Serv ...
- centos6.5使用Google auth进行双因子认证
1.环境 系统:centos6.5 x86_64 [root@uu ~]# uname -a Linux uu 2.6.32-642.el6.x86_64 #1 SMP Wed Apr 13 00:5 ...
- Dockerfile中COPY命令的简单性
dockerfile中的COPY命令是不会拷贝目录结构的,它只会单纯把包含的所有文件拷贝到另一个目录中去. 相关链接:https://www.cnblogs.com/sparkdev/p/957324 ...
- Luogu 3384 【模板】树链剖分
题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式 ...
- linux的一些基础命令
Linux是基于Unix的开源免费的操作系统,是部署服务器的很好选择. 系统:win10 工具:vm虚拟机+Xshell/CRT 虚拟机的系统为linux centos 7 首先看一下linux的基 ...
- 多阶段构建Docker镜像
在Docker 17.05及更高的版本中支持支持一种全新的构建镜像模式:多阶段构建: 多阶段构建Docker镜像的最大好处是使构建出来的镜像变得更小: 目前常见的两个构建镜像的方式为: 1.直接使用某 ...
- 201771010126 王燕《面向对象程序设计(java)》第十八周学习总结
实验十八 总复习 实验时间 2018-12-30 1.实验目的与要求 (1) 综合掌握java基本程序结构: (2) 综合掌握java面向对象程序设计特点: (3) 综合掌握java GUI 程序设 ...
- jieba库的使用与词频统计
1.词频统计 (1)词频分析是对文章中重要词汇出现的次数进行统计与分析,是文本 挖掘的重要手段.它是文献计量学中传统的和具有代表性的一种内容分析方法,基本原理是通过词出现频次多少的变化,来确定热点及其 ...