google cache源码详解
一、引子
缓存有很多种解决方案,常见的是:
1.存储在内存中 : 内存缓存顾名思义直接存储在JVM内存中,JVM宕机那么内存丢失,读写速度快,但受内存大小的限制,且有丢失数据风险。
2.存储在磁盘中: 即从内存落地并序列化写入磁盘的缓存,持久化在磁盘,读写需要IO效率低,但是安全。
3.内存+磁盘组合方式:这种组合模式有很多成熟缓存组件,也是高效且安全的策略,比如redis。
本文分析常用的内存缓存:google cache。源码包:com.google.guava:guava:22.0 jar包下的pcom.google.common.cache包,适用于高并发读写场景,可自定义缓存失效策略。
二、使用方法
2.1 CacheBuilder有3种失效重载模式
1.expireAfterWrite
当 创建 或 写之后的 固定 有效期到达时,数据会被自动从缓存中移除,源码注释如下:
/**指明每个数据实体:当 创建 或 最新一次更新 之后的 固定值的 有效期到达时,数据会被自动从缓存中移除
* Specifies that each entry should be automatically removed from the cache once a fixed duration
* has elapsed after the entry's creation, or the most recent replacement of its value.
*当间隔被设置为0时,maximumSize设置为0,忽略其它容量和权重的设置。这使得测试时 临时性地 禁用缓存且不用改代码。
* <p>When {@code duration} is zero, this method hands off to {@link #maximumSize(long)
* maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be
* useful in testing, or to disable caching temporarily without a code change.
*过期的数据实体可能会被Cache.size统计到,但不能进行读写,数据过期后会被清除。
* <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or
* write operations. Expired entries are cleaned up as part of the routine maintenance described
* in the class javadoc.
*
* @param duration the length of time after an entry is created that it should be automatically
* removed
* @param unit the unit that {@code duration} is expressed in
* @return this {@code CacheBuilder} instance (for chaining)
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the time to live or time to idle was already set
*/
public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
checkState(
expireAfterWriteNanos == UNSET_INT,
"expireAfterWrite was already set to %s ns",
expireAfterWriteNanos);
checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);
this.expireAfterWriteNanos = unit.toNanos(duration);
return this;
}
2.expireAfterAccess
指明每个数据实体:当 创建 或 写 或 读 之后的 固定值的有效期到达时,数据会被自动从缓存中移除。读写操作都会重置访问时间,但asMap方法不会。源码注释如下:
/**指明每个数据实体:当 创建 或 更新 或 访问 之后的 固定值的有效期到达时,数据会被自动从缓存中移除。读写操作都会重置访问时间,但asMap方法不会。
* Specifies that each entry should be automatically removed from the cache once a fixed duration
* has elapsed after the entry's creation, the most recent replacement of its value, or its last
* access. Access time is reset by all cache read and write operations (including
* {@code Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by operations
* on the collection-views of {@link Cache#asMap}.
* 后面的同expireAfterWrite
* <p>When {@code duration} is zero, this method hands off to {@link #maximumSize(long)
* maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be
* useful in testing, or to disable caching temporarily without a code change.
*
* <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or
* write operations. Expired entries are cleaned up as part of the routine maintenance described
* in the class javadoc.
*
* @param duration the length of time after an entry is last accessed that it should be
* automatically removed
* @param unit the unit that {@code duration} is expressed in
* @return this {@code CacheBuilder} instance (for chaining)
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the time to idle or time to live was already set
*/
public CacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) {
checkState(
expireAfterAccessNanos == UNSET_INT,
"expireAfterAccess was already set to %s ns",
expireAfterAccessNanos);
checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);
this.expireAfterAccessNanos = unit.toNanos(duration);
return this;
}
3.refreshAfterWrite
指明每个数据实体:当 创建 或 写 之后的 固定值的有效期到达时,且新请求过来时,数据会被自动刷新(注意不是删除是异步刷新,不会阻塞读取,先返回旧值,异步重载到数据返回后复写新值)。源码注释如下:
/**指明每个数据实体:当 创建 或 更新 之后的 固定值的有效期到达时,数据会被自动刷新。刷新方法在LoadingCache接口的refresh()申明,实际最终调用的是CacheLoader的reload()
* Specifies that active entries are eligible for automatic refresh once a fixed duration has
* elapsed after the entry's creation, or the most recent replacement of its value. The semantics
* of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling
* {@link CacheLoader#reload}.
* 默认reload是同步方法,所以建议用户覆盖reload方法,否则刷新将在无关的读写操作间操作。
* <p>As the default implementation of {@link CacheLoader#reload} is synchronous, it is
* recommended that users of this method override {@link CacheLoader#reload} with an asynchronous
* implementation; otherwise refreshes will be performed during unrelated cache read and write
* operations.
*
* <p>Currently automatic refreshes are performed when the first stale request for an entry
* occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload}
* and immediately return the new value if the returned future is complete, and the old value
* otherwise.触发刷新操作的请求会阻塞调用reload方法并且当返回的Future完成时立即返回新值,否则返回旧值。
*
* <p><b>Note:</b> <i>all exceptions thrown during refresh will be logged and then swallowed</i>.
*
* @param duration the length of time after an entry is created that it should be considered
* stale, and thus eligible for refresh
* @param unit the unit that {@code duration} is expressed in
* @return this {@code CacheBuilder} instance (for chaining)
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the refresh interval was already set
* @since 11.0
*/
@GwtIncompatible // To be supported (synchronously).
public CacheBuilder<K, V> refreshAfterWrite(long duration, TimeUnit unit) {
checkNotNull(unit);
checkState(refreshNanos == UNSET_INT, "refresh was already set to %s ns", refreshNanos);
checkArgument(duration > 0, "duration must be positive: %s %s", duration, unit);
this.refreshNanos = unit.toNanos(duration);
return this;
}
2.2 测试验证
1)定义一个静态的LoadingCache,用cacheBuilder构造缓存,分别定义了同步load(耗时2秒)和异步reload(耗时2秒)方法。
2)在main方法中,往缓存中设置值,定义3个线程,用CountDownLatch倒计时器模拟3个线程并发读取缓存,最后在主线程分别5秒、0.5秒、2秒时get缓存。
测试代码如下:
package guava; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import java.util.Date;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; /**
* @ClassName guava.LoadingCacheTest
* @Description 注意refresh并不会主动刷新,而是被检索触发更新value,且随时可返回旧值
* @Author denny
* @Date 2018/4/28 下午12:10
*/
public class LoadingCacheTest { // guava线程池,用来产生ListenableFuture
private static ListeningExecutorService service = MoreExecutors.listeningDecorator(
Executors.newFixedThreadPool(10)); /**
* 1.expireAfterWrite:指定时间内没有创建/覆盖时,会移除该key,下次取的时候触发"同步load"(一个线程执行load)
* 2.refreshAfterWrite:指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
* "异步reload"(也是一个线程执行reload)
* 3.expireAfterAccess:指定时间内没有读写,会移除该key,下次取的时候从loading中取
* 区别:指定时间过后,expire是remove该key,下次访问是同步去获取返回新值;
* 而refresh则是指定时间后,不会remove该key,下次访问会触发刷新,新值没有回来时返回旧值
*
* 同时使用:可避免定时刷新+定时删除下次访问载入
*/
private static final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
//.refreshAfterWrite(1, TimeUnit.SECONDS)
.expireAfterWrite(1, TimeUnit.SECONDS)
//.expireAfterAccess(1,TimeUnit.SECONDS)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
System.out.println(Thread.currentThread().getName() +"==load start=="+",时间=" + new Date());
// 模拟同步重载耗时2秒
Thread.sleep(2000);
String value = "load-" + new Random().nextInt(10);
System.out.println(
Thread.currentThread().getName() + "==load end==同步耗时2秒重载数据-key=" + key + ",value="+value+",时间=" + new Date());
return value;
} @Override
public ListenableFuture<String> reload(final String key, final String oldValue)
throws Exception {
System.out.println(
Thread.currentThread().getName() + "==reload ==异步重载-key=" + key + ",时间=" + new Date());
return service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
/* 模拟异步重载耗时2秒 */
Thread.sleep(2000);
String value = "reload-" + new Random().nextInt(10);
System.out.println(Thread.currentThread().getName() + "==reload-callable-result="+value+ ",时间=" + new Date());
return value;
}
});
}
}); //倒计时器
private static CountDownLatch latch = new CountDownLatch(1); public static void main(String[] args) throws Exception { System.out.println("启动-设置缓存" + ",时间=" + new Date());
cache.put("name", "张三");
System.out.println("缓存是否存在=" + cache.getIfPresent("name"));
//休眠
Thread.sleep(2000);
//System.out.println("2秒后"+",时间="+new Date());
System.out.println("2秒后,缓存是否存在=" + cache.getIfPresent("name"));
//启动3个线程
for (int i = 0; i < 3; i++) {
startThread(i);
} // -1直接=0,唤醒所有线程读取缓存,模拟并发访问缓存
latch.countDown();
//模拟串行读缓存
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "休眠5秒后,读缓存="+cache.get("name")+",时间=" + new Date());
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "距离上一次读0.5秒后,读缓存="+cache.get("name")+",时间=" + new Date());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "距离上一次读2秒后,读缓存="+cache.get("name")+",时间=" + new Date());
} private static void startThread(int id) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "...begin" + ",时间=" + new Date());
//休眠,当倒计时器=0时唤醒线程
latch.await();
//读缓存
System.out.println(
Thread.currentThread().getName() + "并发读缓存=" + cache.get("name") + ",时间=" + new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
}); t.setName("Thread-" + id);
t.start();
}
}
结果分析
1.expireAfterWrite:当 创建 或 写 之后的 有效期到达时,数据会被自动从缓存中移除
启动-设置缓存,时间=Thu May 17 17:55:36 CST 2018-->主线程启动,缓存创建完毕并设值,即触发写缓存
缓存是否存在=张三
2秒后,缓存是否存在=null--》设定了1秒自动删除缓存,2秒后缓存不存在
Thread-0...begin,时间=Thu May 17 17:55:38 CST 2018--》38秒时,启动3个线程模拟并发读:三个线程读缓存,由于缓存不存在,阻塞在get方法上,等待其中一个线程去同步load数据
Thread-1...begin,时间=Thu May 17 17:55:38 CST 2018
Thread-2...begin,时间=Thu May 17 17:55:38 CST 2018
Thread-1==load start==,时间=Thu May 17 17:55:38 CST 2018---线程1,同步载入数据load()
Thread-1==load end==同步耗时2秒重载数据-key=name,value=load-2,时间=Thu May 17 17:55:40 CST 2018--线程1,同步载入数据load()完毕!,即40秒时写入数据:load-2
Thread-0并发读缓存=load-2,时间=Thu May 17 17:55:40 CST 2018---线程1同步载入数据load()完毕后,3个阻塞在get方法的线程得到缓存值:load-2
Thread-1并发读缓存=load-2,时间=Thu May 17 17:55:40 CST 2018
Thread-2并发读缓存=load-2,时间=Thu May 17 17:55:40 CST 2018
main==load start==,时间=Thu May 17 17:55:43 CST 2018---主线程访问缓存不存在,执行load()
main==load end==同步耗时2秒重载数据-key=name,value=load-4,时间=Thu May 17 17:55:45 CST 2018---load()完毕!45秒时写入数据:load-4
main休眠5秒后,读缓存=load-4,时间=Thu May 17 17:55:45 CST 2018---主线程得到缓存:load-4
main距离上一次读0.5秒后,读缓存=load-4,时间=Thu May 17 17:55:45 CST 2018--距离上一次写才0.5秒,数据有效:load-4
main==load start==,时间=Thu May 17 17:55:47 CST 2018-47秒时,距离上一次写45秒,超过了1秒,数据无效,再次load()
main==load end==同步耗时2秒重载数据-key=name,value=load-8,时间=Thu May 17 17:55:49 CST 2018--49秒时load()完毕:load-8
main距离上一次读2秒后,读缓存=load-8,时间=Thu May 17 17:55:49 CST 2018--打印get的缓存结果:load-8
2.expireAfterAccess:当 创建 或 写 或 读 之后的 有效期到达时,数据会被自动从缓存中移除
修改测试代码98、99行:
Thread.sleep(700);
System.out.println(Thread.currentThread().getName() + "距离上一次读0.5秒后,读缓存="+cache.get("name")+",时间=" + new Date());
启动-设置缓存,时间=Thu May 17 18:32:38 CST 2018
缓存是否存在=张三
2秒后,缓存是否存在=null
Thread-0...begin,时间=Thu May 17 18:32:40 CST 2018
Thread-1...begin,时间=Thu May 17 18:32:40 CST 2018
Thread-2...begin,时间=Thu May 17 18:32:40 CST 2018
Thread-2==load start==,时间=Thu May 17 18:32:40 CST 2018
Thread-2==load end==同步耗时2秒重载数据-key=name,value=load-6,时间=Thu May 17 18:32:42 CST 2018
Thread-0并发读缓存=load-6,时间=Thu May 17 18:32:42 CST 2018
Thread-1并发读缓存=load-6,时间=Thu May 17 18:32:42 CST 2018
Thread-2并发读缓存=load-6,时间=Thu May 17 18:32:42 CST 2018
main==load start==,时间=Thu May 17 18:32:45 CST 2018
main==load end==同步耗时2秒重载数据-key=name,value=load-7,时间=Thu May 17 18:32:47 CST 2018----47秒时写
main休眠5秒后,读缓存=load-7,时间=Thu May 17 18:32:47 CST 2018
main距离上一次读0.5秒后,读缓存=load-7,时间=Thu May 17 18:32:48 CST 2018---48秒读
main距离上一次读0.5秒后,读缓存=load-7,时间=Thu May 17 18:32:49 CST 2018--49秒距离上一次写47秒,间距大于2秒,但是没有触发load() ,因为48秒时又读了一次,刷新了缓存有效期
3.refreshAfterWrite:当 创建 或 写 之后的 有效期到达时,数据会被自动刷新(注意不是删除是刷新)。
启动-设置缓存,时间=Thu May 17 18:39:59 CST 2018--》59秒写
缓存是否存在=张三
main==reload ==异步重载-key=name,时间=Thu May 17 18:40:01 CST 2018--》01秒,2秒后距离上次写超过1秒,reload异步重载
2秒后,缓存是否存在=张三--》距离上一次写过了2秒,但是会立即返回缓存
Thread-0...begin,时间=Thu May 17 18:40:01 CST 2018--》01秒3个线程并发访问
Thread-1...begin,时间=Thu May 17 18:40:01 CST 2018
Thread-2...begin,时间=Thu May 17 18:40:01 CST 2018
Thread-2并发读缓存=张三,时间=Thu May 17 18:40:01 CST 2018--》01秒3个线程都立即得到了缓存
Thread-0并发读缓存=张三,时间=Thu May 17 18:40:01 CST 2018
Thread-1并发读缓存=张三,时间=Thu May 17 18:40:01 CST 2018
pool-1-thread-1==reload-callable-result=reload-5,时间=Thu May 17 18:40:03 CST 2018--》01秒时的异步,2秒后也就是03秒时,查询结果:reload-5
main==reload ==异步重载-key=name,时间=Thu May 17 18:40:06 CST 2018--》06秒时,距离上一次写时间超过1秒,reload异步重载
main休眠5秒后,读缓存=reload-5,时间=Thu May 17 18:40:06 CST 2018--》06秒时,reload异步重载,立即返回旧值reload-5
main距离上一次读0.5秒后,读缓存=reload-5,时间=Thu May 17 18:40:07 CST 2018
main距离上一次读0.5秒后,读缓存=reload-5,时间=Thu May 17 18:40:07 CST 2018
pool-1-thread-2==reload-callable-result=reload-4,时间=Thu May 17 18:40:08 CST 2018--》06秒时的异步重载,2秒后也就是08秒,查询结果:reload-4
三、源码剖析
前面一节简单演示了google cache的几种用法,本节细看源码。
3.1 简介
我们就从构造器CacheBuilder的源码注释,来看一下google cache的简单介绍:
//LoadingCache加载缓存和缓存实例是以下的特性的组合:
1 A builder of LoadingCache and Cache instances having any combination of the following features:
automatic loading of entries into the cache-》把数据实体自动载入到缓存中去-》基本特性
least-recently-used eviction when a maximum size is exceeded-》当缓存到达最大数量时回收最少使用的数据-》限制最大内存,避免内存被占满-》高级特性,赞google cache源码详解的更多相关文章
- Guava Cache源码详解
目录 一.引子 二.使用方法 2.1 CacheBuilder有3种失效重载模式 2.2 测试验证 三.源码剖析 3.1 简介 3.2 源码剖析 四.总结 优点: 缺点: 正文 回到顶部 一.引子 缓 ...
- spring事务详解(三)源码详解
系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...
- [转]Linux内核源码详解--iostat
Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...
- saltstack源码详解一
目录 初识源码流程 入口 1.grains.items 2.pillar.items 2/3: 是否可以用python脚本实现 总结pillar源码分析: @(python之路)[saltstack源 ...
- 源码详解系列(七) ------ 全面讲解logback的使用和源码
什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...
- RocketMQ源码详解 | Broker篇 · 其三:CommitLog、索引、消费队列
概述 上一章中,已经介绍了 Broker 的文件系统的各个层次与部分细节,本章将继续了解在逻辑存储层的三个文件 CommitLog.IndexFile.ConsumerQueue 的一些细节.文章最后 ...
- RocketMQ源码详解 | Consumer篇 · 其一:消息的 Pull 和 Push
概述 当消息被存储后,消费者就会将其消费. 这句话简要的概述了一条消息的最总去向,也引出了本文将讨论的问题: 消息什么时候才对被消费者可见? 是在 page cache 中吗?还是在落盘后?还是像 K ...
- Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解
Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...
- 条件随机场之CRF++源码详解-预测
这篇文章主要讲解CRF++实现预测的过程,预测的算法以及代码实现相对来说比较简单,所以这篇文章理解起来也会比上一篇条件随机场训练的内容要容易. 预测 上一篇条件随机场训练的源码详解中,有一个地方并没有 ...
随机推荐
- angularjs作用域之transclude
transclude是一个可选的参数.如果设置了,其值必须为true,它的默认值是false.嵌入有时被认为是一个高级主题,但某些情况下它与我们刚刚学习过的作用域之间会有非常好的配合.使用嵌入也会很好 ...
- DB2常用函数
1.char函数 char(current date,ISO)--转换成yyyy-mm-dd char(current date,USA)--转换成mm/dd/yyyy char(current ...
- Python基本数据类型之列表、元组、字典、集合及其魔法
列表 1.列表可存放任何东西,并且可修改 2.列表有序 3.列表支持索引与切片 4.支持for,while循环,所以列表为可迭代对象 5支持in操作,判断元素是否在列表中 6可多重索引嵌套列表 7.字 ...
- python3 数据科学基础
第一章 1.Anaconda(最著名的python数据科学平台) 下面小伙伴们咱们来初初识下Anaconda吧 What is Anaconda???? 回答: (1).科学计算的平台 (2).有很多 ...
- pydev 下Django 1.7 undefined variables from import问题的解决
参考:http://stackoverflow.com/questions/24951029/pydev-django-undefined-variables-from-import 参考上面的帖子认 ...
- Android Gradle 自定义Task 详解
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/76408024 本文出自[赵彦军的博客] 一:Gradle 是什么 Gradle是一 ...
- NewLife.Net——网络压测单机1.88亿tps
NewLife.Net压力测试,峰值4.2Gbps,50万pps,消息大小24字节,消息处理速度1.88亿tps! 共集合20台高配ECS参与测试,主服务器带宽6Gbps.100万pps,16核心64 ...
- 分布式单点登录框架XXL-SSO
<分布式单点登录框架XXL-SSO> 一.简介 1.1 概述 XXL-SSO 是一个分布式单点登录框架.只需要登录一次就可以访问所有相互信任的应用系统. 拥有"轻量级.分布式.跨 ...
- STL-Map 源码剖析
G++ ,cygnus\cygwin-b20\include\g++\stl_map.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Compan ...
- javascript 用函数语句和表达式定义函数的区别详解
通常我们会看到以下两种定义函数的方式: // 函数语句 function fn(str) { console.log(str); }; // 表达式定义 var fnx=function(str) { ...