Guava之Cache使用-Cache&LoadingCache
在使用本地缓存时,我们经常使用ConcurrentMap来实现,但是有时我们会存在一些需求,希望本地缓存数据能够自动过期释放等,在不引入第三方缓存插件(例如:ehcache)下,guava中的cache是一个不二之选
Cache&LoadingCahe
Cache:当不需要自动加载数据时,可直接使用Cache,返回的是LocalCache中的静态类LocalManualCache
LoadingCache:当需要自动加载数据时,可使用LoadingCache,返回的是LocalCache中的静态类LocalLoadingCache
Cache之CacheBuilder参数说明
Cache<String ,String> cache = CacheBuilder.newBuilder()
// 并发级别,代表同时可以写入缓存的线程数,用于创建segment,最大65535,默认4
.concurrencyLevel(6)
// 当缓存项在指定时间范围内没有被读或写就失效
//.expireAfterAccess()
// 当缓存项在指定时间范围内没有被更新就失效,load新值过程中,阻塞查询
//.expireAfterWrite()
// 当缓存项在上一次更新后,多久失效,当load新值过程中,查询返回旧值,可能返回的是很久之前的就值,因为数据更新通过查询触发
// requires a LoadingCache
//.refreshAfterWrite()
// 初始大小,默认16
.initialCapacity(10)
// 设置缓存最大容量,逼近时按照LRU移除
.maximumSize(20)
// 开启统计记录
.recordStats()
// 设置缓存最大权重,逼近时按照LRU移除
//.maximumWeight(100)
// 使用软引用存储value。软引用就是在内存不够是才会按照顺序回收。
//.softValues()
// 使用弱引用存储key,当被垃圾回收的时候,当前key没有其他引用的时候缓存项可以被垃圾回收。
//.weakKeys()
// 使用弱引用存储value,当被垃圾回收的时候,当前value没有其他引用的时候缓存项可以被垃圾回收。
//.weakValues()
// 使用自定义Ticker灵活控制时间,模拟时间流逝
//.ticker(Ticker.systemTicker())
.build();
使用示例
基于容量大小回收maximumSize
当容量逼近最大值时,就开始进行回收,并不是到达最大值时才开始回收,回收策略LRU
public static void test0(){
Cache<String ,String> cache = CacheBuilder.newBuilder()
.maximumSize(20)
.recordStats()
.build();
for (int i = 0; i < 20; i++){
cache.put("key"+i,"value"+i);
}
System.out.println(cache.stats().toString());
System.out.println(cache.size());
outCache(cache.asMap());
}
public static void outCache(Map<String, String> map){
for(Map.Entry<String, String> entry: map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}
执行结果:
CacheStats{hitCount=0, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=3}
17
Key = key12, Value = value12
Key = key6, Value = value6
Key = key18, Value = value18
Key = key15, Value = value15
Key = key16, Value = value16
Key = key10, Value = value10
Key = key9, Value = value9
Key = key8, Value = value8
Key = key7, Value = value7
Key = key17, Value = value17
Key = key19, Value = value19
Key = key13, Value = value13
Key = key0, Value = value0
Key = key3, Value = value3
Key = key11, Value = value11
Key = key14, Value = value14
Key = key5, Value = value5
evictionCount=3 结果中,从第18个开始就进行了缓存驱逐,这是因为内部segment中存在this.totalWeight <= this.maxSegmentWeight判断,该细节会在后面介绍,也可仔细阅读源码了解
缓存记录统计-CacheStats
public static void test1(){
Cache<String ,String> cache = CacheBuilder.newBuilder()
.maximumSize(20)
.recordStats()
.build();
for (int i = 0; i < 10; i++){
cache.put("key"+i,"value"+i);
}
//命中
cache.getIfPresent("key1");
cache.getIfPresent("key2");
//未命中
cache.getIfPresent("key20");
System.out.println(cache.stats().toString());
System.out.println(cache.size());
outCache(cache.asMap());
}
执行结果:
CacheStats{hitCount=2, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
10
Key = key6, Value = value6
Key = key1, Value = value1
Key = key4, Value = value4
Key = key2, Value = value2
Key = key9, Value = value9
Key = key8, Value = value8
Key = key7, Value = value7
Key = key3, Value = value3
Key = key5, Value = value5
Key = key0, Value = value0
hitCount:缓存命中 missCount:缓存丢失命中
基于引用的回收
基于引用回收包括:软引用softValues()、弱引用weakKeys()和weakValues()
以weakValues()为例:
public static void test2(){
Cache<String ,String> cache = CacheBuilder.newBuilder()
.weakValues()
.recordStats()
.build();
for (int i = 0; i < 20; i++){
cache.put("key"+i,"value"+i);
}
String value = cache.getIfPresent("key1");
System.gc();
System.out.println(cache.stats().toString());
System.out.println(cache.size());
outCache(cache.asMap());
}
执行结果:
CacheStats{hitCount=1, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
20
Key = key1, Value = value1
在系统GC时,没有被外部引用的缓存项会被系统回收
但是cache.size()仍然还是不变(20)
定时回收
定时回收包括:expireAfterAccess、expireAfterWrite、refreshAfterWrite
expireAfterAccess:时间范围内,没有进行读或写,则回收
expireAfterWrite:时间范围内,没有进行更新,则回收
refreshAfterWrite:超出时间后过期,只能在LoadingCache中使用
以expireAfterWrite为例:
private static class DemoTicker extends Ticker {
private long start = Ticker.systemTicker().read();
private long offsetTime = 0;
@Override
public long read() {
return start + offsetTime;
}
public void addTime(long offsetTime) {
this.offsetTime = offsetTime;
}
}
public static void test3(){
//模拟时间流逝
DemoTicker demoTicker = new DemoTicker();
Cache<String ,String> cache = CacheBuilder.newBuilder()
.initialCapacity(10)
//.expireAfterAccess(10, TimeUnit.MINUTES)
.expireAfterWrite(10, TimeUnit.MINUTES)
//.refreshAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.ticker(demoTicker)
.build();
for (int i = 0; i < 20; i++){
cache.put("key"+i,"value"+i);
}
demoTicker.addTime(TimeUnit.NANOSECONDS.convert(5,TimeUnit.MINUTES));
//cache.getIfPresent("key1");
cache.put("key1","hehe");
demoTicker.addTime(TimeUnit.NANOSECONDS.convert(10,TimeUnit.MINUTES));
System.out.println(cache.stats().toString());
System.out.println(cache.size());
outCache(cache.asMap());
}
执行结果:
CacheStats{hitCount=0, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
20
Key = key1, Value = hehe
时间访问内未被使用到的缓存项,自动被回收,但是cache.size()仍然还是不变(20)
自动加载
自动加载包括:统一规则load,自定义规则load
统一规则:使用LoadingCache CacheLoader实现
自定义规则:使用Callable callback
public static void test1() throws ExecutionException {
//模拟时间流逝
DemoTicker demoTicker = new DemoTicker();
LoadingCache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(10)
.recordStats()
.refreshAfterWrite(5, TimeUnit.SECONDS)
.ticker(demoTicker)
.build(new CacheLoader<String, String>() {
@Override
public String load(String s) throws Exception {
return RandomStringUtils.randomAlphanumeric(10);
}
});
for (int i = 0; i < 10; i++){
cache.put("key"+i,"value"+i);
}
demoTicker.addTime(TimeUnit.NANOSECONDS.convert(6,TimeUnit.SECONDS));
//get触发更新
cache.getIfPresent("key1"); //使用统一load
//使用自定义单独load
cache.get("key2", new Callable<String>() {
public String call() throws Exception {
return "222";
}
});
System.out.println(cache.size());
System.out.println(cache.stats().toString());
CacheUtil.outCache(cache.asMap());
}
执行结果:
10
CacheStats{hitCount=2, missCount=0, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=32281287, evictionCount=0}
Key = key6, Value = value6
Key = key1, Value = SUNZEIaUH0
Key = key4, Value = value4
Key = key2, Value = 222
Key = key0, Value = value0
Key = key9, Value = value9
Key = key3, Value = value3
Key = key8, Value = value8
Key = key7, Value = value7
Key = key5, Value = value5
元素移除/替换监视
监视事件:
1、当元素由于size或weight逼近上限时发生移除
2、当元素过期重新load替换旧数据
public static void test2() throws ExecutionException {
//模拟时间流逝
DemoTicker demoTicker = new DemoTicker();
LoadingCache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(10)
.refreshAfterWrite(5, TimeUnit.SECONDS)
.ticker(demoTicker)
.recordStats()
.removalListener(new RemovalListener<String, String>() {
public void onRemoval(RemovalNotification<String, String> removalNotification) {
System.out.println(String.format("key=%s,value=%s,reason=%s", removalNotification.getKey(), removalNotification.getValue(), removalNotification.getCause()));
}
})
.build(new CacheLoader<String, String>() {
@Override
public String load(String s) throws Exception {
return RandomStringUtils.randomAlphanumeric(10);
}
});
for (int i = 0; i < 15; i++){
cache.put("key"+i,"value"+i);
}
demoTicker.addTime(TimeUnit.NANOSECONDS.convert(6,TimeUnit.SECONDS));
//get触发更新
cache.getIfPresent("key13");
cache.get("key14", new Callable<String>() {
public String call() throws Exception {
return "222";
}
});
System.out.println(cache.size());
CacheUtil.outCache(cache.asMap());
}
执行结果:
key=key0,value=value0,reason=SIZE
key=key1,value=value1,reason=SIZE
key=key2,value=value2,reason=SIZE
key=key3,value=value3,reason=SIZE
key=key4,value=value4,reason=SIZE
key=key13,value=value13,reason=REPLACED
key=key14,value=value14,reason=REPLACED
10
Key = key12, Value = value12
Key = key6, Value = value6
Key = key13, Value = NU6vlPNRiM
Key = key10, Value = value10
Key = key9, Value = value9
Key = key8, Value = value8
Key = key7, Value = value7
Key = key11, Value = value11
Key = key14, Value = 222
Key = key5, Value = value5