Guava Cache與ConcurrentMap很類似,但也不徹底同樣。最基本的區別是ConcurrentMap會一直保存全部添加的元素,直到顯式地移除。相對地,Guava Cache爲了限制內存佔用,一般都設定爲自動回收元素。在某些場景下,儘管LoadingCache 不回收元素,它也是頗有用的,由於它會自動加載緩存。html
一般來講,Guava Cache適用於:java
若是你的場景符合上述的每一條,Guava Cache就適合你。git
注:若是你不須要Cache中的特性,使用ConcurrentHashMap有更好的內存效率——但Cache的大多數特性都很難基於舊有的ConcurrentMap複製,甚至根本不可能作到。redis
經過這兩種方法建立的cache,和一般用map來緩存的作法比,不一樣在於,這兩種方法都實現了一種邏輯——從緩存中取key X的值,若是該值已經緩存過了,則返回緩存中的值,若是沒有緩存過,能夠經過某個方法來獲取這個值。但不一樣的在於cacheloader的定義比較寬泛,是針對整個cache定義的,能夠認爲是統一的根據key值load value的方法。而callable的方式較爲靈活,容許你在get的時候指定。緩存
方式1、建立 CacheLoader服務器
LoadingCache是附帶CacheLoader構建而成的緩存實現。建立本身的CacheLoader一般只須要簡單地實現V load(K key) throws Exception方法。例如,你能夠用下面的代碼構建LoadingCache:
CacheLoader: 當檢索不存在的時候,會自動的加載信息的異步
class Person{ private String name; public Person(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } } public com.google.common.cache.CacheLoader<String, Person> createCacheLoader() { return new com.google.common.cache.CacheLoader<String, Person>() { @Override public Person load(String key) throws Exception { System.out.println("加載建立key:" + key); return new Person(key+":ddd"); } }; } @Test public void testCreateCacheLoader() throws ExecutionException { LoadingCache<String, Person> cache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(createCacheLoader()); cache.put("aa",new Person("aaa")); Person aa = cache.get("aa"); System.out.println(aa);//Person{name='aaa'} Person bb = cache.get("bb"); System.out.println(bb); //加載建立key:bb Person{name='bb:ddd'} }
方式2、建立 Callableasync
@Test public void testCreateCallable() throws Exception { Cache<String, Person> cache = CacheBuilder.newBuilder() .maximumSize(1000) .build(); // look Ma, no CacheLoader try { cache.put("aa", new Person("aaaa")); // If the key wasn't in the "easy to compute" group, we need to // do things the hard way. Person aa = cache.get("aa", new Callable<Person>() { @Override public Person call() throws Exception { return new Person("defalut"); // return doThingsTheHardWay(key); } }); System.out.println(aa);//Person{name='aaaa'} } catch (Exception e) { throw new Exception(e.getCause()); } Person bb = cache.get("bb", () -> new Person("defalut")); System.out.println(bb); //Person{name='defalut'} }
cache的參數ide
基本方法介紹函數
一、getIfPresent(Object key); 該方法從本地緩存中找值,若是找不到返回null,找到就返回相應的值。
二、get:首先會在緩存中找,緩存中找不到再經過load加載。
三、remove(@Nullable Object key);調用LocalManualCache的invalidate(Object key)方法便可調用remove.
四、evictEntries(ReferenceEntry<K, V> newest);傳入的參數爲最新的Entry,多是剛插入的,也多是剛更新過的。
該方法只有在設置了在構建緩存的時候指定了maximumSize纔會往下執行。首先清除recencyQueue,判斷該元素自身的權重是否超過上限,若是超過則移除當前元素。而後判斷總的權重是否大於上限,若是超過則去accessQueue裏找到隊首(即最不常訪問的元素)進行
五、preWriteCleanup(long now);傳人蔘數只有當前時間。鍵值引用隊列中都是存儲已經被GC,等待清除的entry信息,因此首先去處理這個裏面的entry.
讀寫隊列裏面是按照讀寫時間排序的,取出隊列中的首元素,若是當前時間與該元素的時間相差值大於設定值,則進行回收。
六、put
public V put(K key, V value); //onlyIfAbsent爲false
public V putIfAbsent(K key, V value); //onlyIfAbsent爲true
該方法顯式往本地緩存裏面插入值。從下面的流程圖中能夠看出,在執行每次put前都會進行preWriteCleanUP,在put返回前若是更新了entry則要進行evictEntries操做。
七、getUnchecked
若是你的CacheLoader沒有定義任何checked Exception,那你可使用getUnchecked。
使用cache.put(key, value)方法能夠直接向緩存中插入值,這會直接覆蓋掉給定鍵以前映射的值。使用Cache.asMap()視圖提供的任何方法也能修改緩存。但請注意,asMap視圖的任何方法都不能保證緩存項被原子地加載到緩存中
進一步說,asMap視圖的原子運算在Guava Cache的原子加載範疇以外,因此相比於Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable<V>) 應該老是優先使用。
Guava Cache提供了三種基本的緩存回收方式:基於容量回收、定時回收和基於引用回收。
大小
若是要規定緩存項的數目不超過固定值,只需使用CacheBuilder.maximumSize(long)。緩存將嘗試回收最近沒有使用或整體上不多使用的緩存項。
警告:在緩存項的數目達到限定值以前,緩存就可能進行回收操做——一般來講,這種狀況發生在緩存項的數目逼近限定值時。
權重
另外,不一樣的緩存項有不一樣的「權重」(weights)——例如,若是你的緩存值,佔據徹底不一樣的內存空間,你可使用CacheBuilder.weigher(Weigher)指定一個權重函數,而且用CacheBuilder.maximumWeight(long)指定最大總重。在權重限定場景中,除了要注意回收也是在重量逼近限定值時就進行了,還要知道重量是在緩存建立時計算的,所以要考慮重量計算的複雜度。
@Test public void testWeight() throws Exception { LoadingCache<String, Person> cache = CacheBuilder.newBuilder() .maximumWeight(5) .weigher((Weigher<String, Person>) (s, person) -> { //權重計算器 int weight = person.name.length(); System.out.println("key:"+s); return weight; }) .build(new CacheLoader<String, Person>() { @Override public Person load(String key) { System.out.println("加載建立key:" + key); return new Person(key + ":default"); } }); cache.put("a",new Person("aaaaaaa1")); cache.put("b",new Person("bbbbbb1")); cache.put("c",new Person("cc1")); Person a = cache.get("a"); System.out.println(a); Person b = cache.get("b"); System.out.println(b); Person c = cache.get("c"); System.out.println(c); //緩存只有 一個 c System.out.println(cache.asMap()); }
輸出:
key:a key:b key:c 加載建立key:a key:a Person{name='a:default'} 加載建立key:b key:b Person{name='b:default'} Person{name='cc1'} {c=Person{name='cc1'}}
二、定時回收(Timed Eviction)
CacheBuilder提供兩種定時回收的方法:
以下文所討論,定時回收週期性地在寫操做中執行,偶爾在讀操做中執行。
@Test public void testEvictionByAccessTime() throws ExecutionException, InterruptedException { LoadingCache<String, Person> cache = CacheBuilder.newBuilder() .expireAfterAccess(2, TimeUnit.SECONDS) .build(createCacheLoader()); cache.getUnchecked("wangji"); TimeUnit.SECONDS.sleep(3); Person employee = cache.getIfPresent("wangji"); //不會從新加載建立cache System.out.println("被銷燬:" + (employee == null ? "是的" : "否")); cache.getUnchecked("guava"); TimeUnit.SECONDS.sleep(1); employee = cache.getIfPresent("guava"); //會從新加載建立cache System.out.println("被銷燬:" + (employee == null ? "是的" : "否")); TimeUnit.SECONDS.sleep(2); employee = cache.getIfPresent("guava"); //不會從新加載建立cache System.out.println("被銷燬:" + (employee == null ? "是的" : "否")); TimeUnit.SECONDS.sleep(2); employee = cache.getIfPresent("guava"); //不會從新加載建立cache System.out.println("被銷燬:" + (employee == null ? "是的" : "否")); TimeUnit.SECONDS.sleep(2); employee = cache.getIfPresent("guava"); //不會從新加載建立cache System.out.println("被銷燬:" + (employee == null ? "是的" : "否")); }
輸出
加載建立key:wangji
被銷燬:是的
加載建立key:guava
被銷燬:否
被銷燬:是的
被銷燬:是的
被銷燬:是的
三、基於引用的回收(Reference-based Eviction)【強(strong)、軟(soft)、弱(weak)、虛(phantom】
經過使用弱引用的鍵、或弱引用的值、或軟引用的值,Guava Cache能夠把緩存設置爲容許垃圾回收:
@Test public void testWeakKey() throws ExecutionException, InterruptedException { LoadingCache<String, Person> cache = CacheBuilder.newBuilder() // .weakValues() .weakKeys() .softValues() .build(createCacheLoader()); cache.getUnchecked("guava"); cache.getUnchecked("wangji"); System.gc(); TimeUnit.MILLISECONDS.sleep(100); Person employee = cache.getIfPresent("guava"); //不會從新加載建立cache System.out.println("被銷燬:" + (employee == null ? "是的" : "否")); }
輸出:
加載建立key:guava
加載建立key:wangji
被銷燬:否
任什麼時候候,你均可以顯式地清除緩存項,而不是等到它被回收:
個別清除:Cache.invalidate(key)
批量清除:Cache.invalidateAll(keys)
清除全部緩存項:Cache.invalidateAll()
清理何時發生
使用CacheBuilder構建的緩存不會」自動」執行清理和回收工做,也不會在某個緩存項過時後立刻清理,也沒有諸如此類的清理機制。相反,它會在寫操做時順帶作少許的維護工做,或者偶爾在讀操做時作——若是寫操做實在太少的話。
這樣作的緣由在於:若是要自動地持續清理緩存,就必須有一個線程,這個線程會和用戶操做競爭共享鎖。此外,某些環境下線程建立可能受限制,這樣CacheBuilder就不可用了。
相反,咱們把選擇權交到你手裏。若是你的緩存是高吞吐的,那就無需擔憂緩存的維護和清理等工做。若是你的 緩存只會偶爾有寫操做,而你又不想清理工做阻礙了讀操做,那麼能夠建立本身的維護線程,以固定的時間間隔調用Cache.cleanUp()。
ScheduledExecutorService能夠幫助你很好地實現這樣的定時調度。
刷新
刷新和回收不太同樣。正如LoadingCache.refresh(K)所聲明,刷新表示爲鍵加載新值,這個過程能夠是異步的。在刷新操做進行時,緩存仍然能夠向其餘線程返回舊值,而不像回收操做,讀緩存的線程必須等待新值加載完成。
@Test public void testCacheRefresh() throws InterruptedException { AtomicInteger counter = new AtomicInteger(0); CacheLoader<String, Long> cacheLoader = CacheLoader.from(k -> { counter.incrementAndGet(); System.out.println("建立 key :" + k); return System.currentTimeMillis(); }); LoadingCache<String, Long> cache = CacheBuilder.newBuilder() .refreshAfterWrite(2, TimeUnit.SECONDS) // 2s後從新刷新 .build(cacheLoader); Long result1 = cache.getUnchecked("guava"); TimeUnit.SECONDS.sleep(3); Long result2 = cache.getUnchecked("guava"); System.out.println(result1.longValue() != result2.longValue() ? "是的" : "否"); }
CacheBuilder.refreshAfterWrite(long, TimeUnit)能夠爲緩存增長自動定時刷新功能。和expireAfterWrite相反,refreshAfterWrite經過定時刷新可讓緩存項保持可用,但請注意:緩存項只有在被檢索時纔會真正刷新(若是CacheLoader.refresh實現爲異步,那麼檢索不會被刷新拖慢)。所以,若是你在緩存上同時聲明expireAfterWrite和refreshAfterWrite,緩存並不會由於刷新盲目地定時重置,若是緩存項沒有被檢索,那刷新就不會真的發生,緩存項在過時時間後也變得能夠回收。
經過CacheBuilder.removalListener(RemovalListener),你能夠聲明一個監聽器,以便緩存項被移除時作一些額外操做。緩存項被移除時,RemovalListener會獲取移除通知[RemovalNotification],其中包含移除緣由[RemovalCause]、鍵和值。
@Test public void testCacheRemovedNotification() { CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase); RemovalListener<String, String> listener = notification -> { if (notification.wasEvicted()) { RemovalCause cause = notification.getCause(); System.out.println("remove cause is :" + cause.toString()); System.out.println("key:" + notification.getKey() + "value:" + notification.getValue()); } }; LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(3) .removalListener(listener)// 添加刪除監聽 .build(loader); cache.getUnchecked("lhx"); cache.getUnchecked("wangwang"); cache.getUnchecked("guava"); cache.getUnchecked("test"); cache.getUnchecked("test1"); }
輸出
remove cause is :SIZE
key:lhxvalue:LHX
remove cause is :SIZE
key:wangwangvalue:WANGWANG
警告:默認狀況下,監聽器方法是在移除緩存時同步調用的。由於緩存的維護和請求響應一般是同時進行的,代價高昂的監聽器方法在同步模式下會拖慢正常的緩存請求。在這種狀況下,你可使用RemovalListeners.asynchronous(RemovalListener, Executor)把監聽器裝飾爲異步操做。
請注意,RemovalListener拋出的任何異常都會在記錄到日誌後被丟棄[swallowed]。