guava cache簡介
為什么會有g(shù)uava cache
實(shí)際開發(fā)中漏健,有時候會有一些不常修改嵌灰,但是經(jīng)常會被用到的數(shù)據(jù),它們可能放在數(shù)據(jù)庫里,也可能放在配置文件等。每次用到它們,如果都去查數(shù)據(jù)庫冰蘑,讀取配置文件的話堪滨,那么效率是比較慢的鞭达。我們可以把數(shù)據(jù)集中放到一個地方碴犬,每次查詢的時候就去那個地方查贞岭。這個地方可以是redis错维,相比查數(shù)據(jù)庫波俄,redis效率顯然會更快,但是還是得跨網(wǎng)絡(luò)澡刹。這個地方也可以是JVM內(nèi)存胜蛉,在所有的存放數(shù)據(jù)的地方中,內(nèi)存無疑是最快的昆著。存放數(shù)據(jù),又是jvm內(nèi)存,你可能會想到HashMap瞧预,如果是比較簡單的需求正压,HashMap已經(jīng)滿足我們的需求了郑临;但是如果你存放數(shù)據(jù)的同時省店,又要求可能像redis一樣可以設(shè)置過期時間,還要求一定要線程安全捉腥,這時候HashMap可能就有點(diǎn)力不從心了氓拼。而guava cache就是這樣一款可以把數(shù)據(jù)存放到JVM內(nèi)存,還具有設(shè)置過期時間等功能的緩存工具但狭。
guava cache
你可以把 guava cache當(dāng)做一個可以設(shè)定過期機(jī)制披诗,多線程安全的map;實(shí)際上立磁,guava cache底層的LocalCache也確實(shí)是繼承了ConcurrentMap。
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
...
}
demo
pom文件
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
java代碼
public static void main(String[] args) throws Exception {
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
return getValue(key);
}
});
System.out.println("key.length = " + cache.get("hello"));
}
private static Object getValue(String key) {
return key.length();
}
利用CacheBuilder.newBuilder().build(new CacheLoader<String, Object>(){...})即可創(chuàng)建一個LocalCache剥槐,build里面的new CacheLoader<String, Object>(){...}是根據(jù)key加載獲取value的方式唱歧,我們可以調(diào)取rmi加載獲取相關(guān)信息,也可以從數(shù)據(jù)庫加載獲取信息粒竖,還可以從配置文件加載獲取信息颅崩,獲取到value以后,LocalCache會把數(shù)據(jù)保存到內(nèi)存中蕊苗,如果第二次獲取該key的數(shù)據(jù)的話沿后,直接從內(nèi)存中讀取數(shù)據(jù)返回,而不用再次加載了朽砰。我這里做演示用尖滚,所以只是保存了key的長度。
guava cache配置
加載與插入
guava cache和hashMap最大的不同就是瞧柔,hashMap想要get某個key對應(yīng)的value的時候漆弄,要先顯式把value put進(jìn)去,但是guava cache有一個加載機(jī)制造锅,在創(chuàng)建LocalCache或者get 的時候傳入CacheLoader或者Callable實(shí)例撼唾,每次我們get,guava cache會首先從cache里面查找是否有key對應(yīng)的value哥蔚,如果有倒谷,返回蛛蒙,如果沒有,會調(diào)用LocalCache或者callable加載key對應(yīng)的value渤愁,然后隱式的put到cache中并且返回宇驾,從而我們不用先顯式把value put 進(jìn)去。
- CacheLoader
public static void main(String[] args) throws Exception {
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
return getValue(key);
}
});
一般我們可以通過cache.get(key)來獲取key對應(yīng)的value猴伶,但是這個辦法會拋出ExecutionException 的異常课舍,如果我們不想每次都寫個try catch來處理ExecutionException ,我們可以用cache.getUnchecked(key),如果用getUnchecked他挎,CacheLoader加載器中不要聲明任何檢查型異常筝尾;批量獲取可以用getAll(Iterable<? extends K>)。
- Callable
Cache<String, Object> cache = CacheBuilder.newBuilder().build();
cache.put("hello", "hi");
System.out.println(cache.get("hello", new Callable<Object>() {
@Override
public Object call() throws Exception {
return "hello";
}
}));
我的理解是办桨,這種加載辦法是對CacheLoader筹淫,或者put的一種補(bǔ)充,如果get找不到對應(yīng)的value(即返回null)呢撞,就會調(diào)用Callable的回調(diào)方法call损姜。比如:
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
return 2;
}
});
//如果上面的load return 2,這里的sout會輸出2殊霞, 如果上面的load return null摧阅,這里的sout會輸出1。
System.out.println(cache.get("hello", new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
}));
- put
guava cache也可以像我們普通的map那樣顯式把key绷蹲,value put到cache中棒卷。
回收
guava cache 和 hashMap第二個不同就是,guava cache有很多種回收機(jī)制可以隱式移除某些key祝钢,而在hashMap中比规,我們只能顯式的移除key(map.remove(key))。
- 基于容量的回收
CacheBuilder.maximumSize(long)
設(shè)置cache的大小拦英,當(dāng)cache存放的元素超過最大值的時候蜒什,最先放入cache的元素會被剔除掉。
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
System.out.println("--load key =" + key +" --");
return key.length();
}
});
System.out.println(cache.get("a"));
System.out.println(cache.get("a"));
System.out.println(cache.get("b"));
System.out.println(cache.get("c"));
System.out.println(cache.get("d"));
System.out.println(cache.get("a"));
//我們設(shè)置了cache最多能存放3個元素疤估,a是最先放進(jìn)來的灾常,當(dāng)放完b,c,d以后,再次取a的時候還是需要加載做裙。
- 定時回收
- expireAfterAccess(long, TimeUnit):緩存項(xiàng)在給定時間內(nèi)沒有被讀/寫訪問岗憋,則回收。請注意這種緩存的回收順序和基于大小回收一樣锚贱。
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.expireAfterAccess(4, TimeUnit.SECONDS)
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
System.out.println("--load key =" + key +" --");
return key.length();
}
});
System.out.println(cache.get("a"));
Thread.sleep(3000);
System.out.println(cache.get("a"));
Thread.sleep(3000);
System.out.println(cache.get("a"));
Thread.sleep(4100);
System.out.println(cache.get("a"));
//第一次get會load仔戈,第二次,第三次都不會load,第四次因?yàn)檫^了4秒了监徘,所以會load晋修。
- expireAfterWrite(long, TimeUnit):緩存項(xiàng)在給定時間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則回收凰盔。如果認(rèn)為緩存數(shù)據(jù)總是在固定時候后變得陳舊不可用墓卦,這種回收方式是可取的。
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.expireAfterWrite(4, TimeUnit.SECONDS)
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
System.out.println("--load key =" + key +" --");
return key.length();
}
});
System.out.println(cache.get("a"));
Thread.sleep(3000);
System.out.println(cache.get("a"));
Thread.sleep(3000);
System.out.println(cache.get("a"));
Thread.sleep(4100);
System.out.println(cache.get("a"));
//第一次get會load户敬,第二次因?yàn)檫€沒到4s落剪,所以不會load,第三次會load尿庐,因?yàn)榫嚯x第一次get已經(jīng)有6秒忠怖,超過4秒了,第四次也會load抄瑟,因?yàn)榫嚯x第二次get超過了4s凡泣。
- 基于引用的回收
通過使用弱引用的鍵、或弱引用的值皮假、或軟引用的值鞋拟,Guava Cache可以把緩存設(shè)置為允許垃圾回收:
- CacheBuilder.weakKeys():使用弱引用存儲鍵。當(dāng)鍵沒有其它(強(qiáng)或軟)引用時惹资,緩存項(xiàng)可以被垃圾回收贺纲。因?yàn)槔厥諆H依賴恒等式(==),使用弱引用鍵的緩存用==而不是equals比較鍵布轿。
LoadingCache<Cat, Integer> cache = CacheBuilder.newBuilder()
.weakKeys()
.build(new CacheLoader<Cat, Integer>() {
@Override
public Integer load(Cat cat) throws Exception {
System.out.println("--load key =" + cat.getName() + " --");
return cat.getAge();
}
});
// 到最后我們會發(fā)現(xiàn)哮笆,當(dāng)cache.size達(dá)到4個以后就不會再增加了,這是因?yàn)閚ew Cat("cat:" + i, i + 10000)沒有被任何東西引用到汰扭,每次gc就會被回收掉,如果被gc掉的話福铅,則cache中的key也會沒了萝毛,所以cache.size才不會源源不斷的增長。至于為什么還有4只滑黔,我也不太清楚
int i = 1;
while (true) {
cache.get(new Cat("cat:" + i, i + 10000));
System.out.println(cache.size());
System.gc();
Thread.sleep(1000);
}
//如果我們用這段代碼笆包,會發(fā)現(xiàn)即使有g(shù)c,cache.size還是會源源不斷的增長略荡,這是因?yàn)閏at會放到了list中庵佣,有被引用到。
int i = 1;
List<Cat> cats = new ArrayList<>();
while (true) {
Cat c = new Cat("cat:" + i, i + 10000);
cats.add(c);
cache.get(c);
System.out.println(cache.size());
System.gc();
Thread.sleep(1000);
}
- CacheBuilder.weakValues():使用弱引用存儲值汛兜。當(dāng)值沒有其它(強(qiáng)或軟)引用時巴粪,緩存項(xiàng)可以被垃圾回收。因?yàn)槔厥諆H依賴恒等式(==),使用弱引用值的緩存用==而不是equals比較值肛根。
- CacheBuilder.softValues():使用軟引用存儲值辫塌。軟引用只有在響應(yīng)內(nèi)存需要時,才按照全局最近最少使用的順序回收派哲【拾保考慮到使用軟引用的性能影響,我們通常建議使用更有性能預(yù)測性的緩存大小限定(見上文芭届,基于容量回收)储矩。使用軟引用值的緩存同樣用==而不是equals比較值。
- 顯式清除
- 個別清除:Cache.invalidate(key)
- 批量清除:Cache.invalidateAll(keys)
- 清除所有緩存項(xiàng):Cache.invalidateAll()
- 移除監(jiān)聽器
我們可以設(shè)置移除監(jiān)聽器褂乍,當(dāng)key被移除的時候(invalidate)會觸發(fā)監(jiān)聽器持隧。
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.removalListener(new RemovalListener<String, Integer>() {
@Override
public void onRemoval(RemovalNotification<String, Integer> removalNotification) {
System.out.println("remove key = " + removalNotification.getKey() + ", value = " + removalNotification.getValue());
}
})
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
System.out.println("--load key =" + key + " --");
return key.length();
}
});
cache.get("hello");
cache.invalidate("hello");
- 何時移除
guava cache從來不會主動會回收不符合我們設(shè)置回收機(jī)制的key-value,即不會開一個線程去回收树叽,比如我們設(shè)置了expireAfterAccess(5s),某個鍵值達(dá)到了5s以后舆蝴,并不會觸動監(jiān)聽器,只有當(dāng)5s以后题诵,我們再次調(diào)用該key的時候才會觸發(fā)該監(jiān)聽器洁仗,就算是調(diào)用其他key也不會回收該key。
統(tǒng)計
-
recordStats()
開啟統(tǒng)計 -
asMap()
獲取key value的map
原理
LocalCache的數(shù)據(jù)結(jié)構(gòu)與ConcurrentHashMap很相似性锭,都由多個segment組成赠潦,且各個segment相對獨(dú)立,互不影響草冈,所以能支持并行操作她奥。每個segment由一個table和若干隊(duì)列組成凸丸。緩存數(shù)據(jù)存儲在table中阳堕,其類型為AtomicReferenceArray。