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