緩存在不少場景下都是須要使用的。好比在須要一個值的過程和代價特別高的狀況下,並且對這個值的須要不止一次的狀況下,咱們可能就須要考慮使用緩存了。java
通常來講要使用本地緩存,首先,是緩存中的數據總量不會超過內存的容量。而且你願意消耗一些內存來提高速度。緩存
通常來講咱們能夠直接使用jdk裏提供的數據結構來做爲緩存,但這樣有個問題就是緩存的一些機制,好比緩存過時的淘汰策略,緩存的初始化,緩存最大容量的設置,緩存的共享等等一些列的問題須要本身去考慮和實現。數據結構
第二種方法就是咱們可使用一些業界開源的,成熟的一些第三方的工具來幫助咱們實現緩存。這其中有:EHCache,cahce4j等等好多框架和工具。但從我使用的來看我認爲google裏guava包內的緩存工具是我使用過的最方便,簡單的緩存框架。框架
下面就來介紹這個Guava包內的CacheBuilder。異步
LoadingCache是附帶CacheLoader構建而成的緩存實現。建立本身的CacheLoader一般只須要簡單地實現V load(K key) throws Exception方法。(固然你也能夠從新實現Cacheloder裏的其餘方法,來擴展你緩存的功能,好比loadAll,reload等。)async
簡單的一個例子:函數
LoadingCache<Key, String> graphs = CacheBuilder.newBuilder().maximumSize(2000).build( new CacheLoader<Key, String>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } }); ... ... try { return graphs.get(key); } catch (ExecutionException e) { throw new OtherException(e.getCause()); }
因爲CacheLoader可能拋出異常,LoadingCache.get(K)也聲明爲拋出ExecutionException異常。若是你定義的CacheLoader沒有聲明任何檢查型異常,則能夠經過getUnchecked(K)查找緩存;但必須注意,一旦CacheLoader聲明瞭檢查型異常,就不能夠調用getUnchecked(K)。工具
ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException;
這個方法用來執行批量查詢。默認狀況下,對每一個不在緩存中的鍵,getAll方法會單獨調用CacheLoader.load
來加載緩存項。若是批量的加載比多個單獨加載更高效,你能夠重載CacheLoader.loadAll
來利用這一點。getAll(Iterable)的性能也會相應提高。性能
get(K, Callable<V>)
這個方法不論有沒有實現自動加載均可以使用。代碼用例以下:ui
Cache<Key, Graph> cache = CacheBuilder.newBuilder()
.maximumSize(1000) .build(); // look Ma, no CacheLoader ... try { // If the key wasn't in the "easy to compute" group, we need to // do things the hard way. cache.get(key, () -> doThingsTheHardWay(key)); } catch (ExecutionException e) { throw new OtherException(e.getCause()); }
tip:在整個加載方法完成前,緩存項相關的可觀察狀態都不會更改。這個方法簡便地實現了模式"若是有緩存則返回;不然運算、緩存、而後返回"。
使用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)指定最大總重。在權重限定場景中,除了要注意回收也是在重量逼近限定值時就進行了,還要知道重量是在緩存建立時計算的,所以要考慮重量計算的複雜度。。
當cache中全部的「weight」總和達到maxKeyWeight時,將會觸發「剔除策略」。
CacheBuilder提供兩種定時回收的方法:
expireAfterAccess(long, TimeUnit)
:緩存項在給定時間內沒有被讀/寫訪問,則回收。請注意這種緩存的回收順序和基於大小回收同樣。
expireAfterWrite(long, TimeUnit)
:緩存項在給定時間內沒有被寫訪問(建立或覆蓋),則回收。若是認爲緩存數據老是在固定時候後變得陳舊不可用,這種回收方式是可取的。
經過弱引用的鍵或者弱引用的值,或者軟引用的值,guava Cache能夠把緩存設置爲容許垃圾回收
任什麼時候候,你均可以顯式地清除緩存項,而不是等到它被回收:
經過CacheBuilder.removalListener(RemovalListener),你能夠聲明一個監聽器,以便緩存項被移除時作一些額外操做。緩存項被移除時,RemovalListener會獲取移除通知[RemovalNotification],其中包含移除緣由[RemovalCause]、鍵和值。
請注意,RemovalListener拋出的任何異常都會在記錄到日誌後被丟棄
警告:默認狀況下,監聽器方法是在移除緩存時同步調用的。由於緩存的維護和請求響應一般是同時進行的,代價高昂的監聽器方法在同步模式下會拖慢正常的緩存請求。在這種狀況下,你可使用RemovalListeners.asynchronous(RemovalListener, Executor)把監聽器裝飾爲異步操做。
使用CacheBuilder構建的緩存不會"自動"執行清理和回收工做,也不會在某個緩存項過時後立刻清理,也沒有諸如此類的清理機制。相反,它會在寫操做時順帶作少許的維護工做,或者偶爾在讀操做時作——若是寫操做實在太少的話。
這樣作的緣由在於:若是要自動地持續清理緩存,就必須有一個線程,這個線程會和用戶操做競爭共享鎖。此外,某些環境下線程建立可能受限制,這樣CacheBuilder就不可用了。
相反,咱們把選擇權交到你手裏。若是你的緩存是高吞吐的,那就無需擔憂緩存的維護和清理等工做。若是你的 緩存只會偶爾有寫操做,而你又不想清理工做阻礙了讀操做,那麼能夠建立本身的維護線程,以固定的時間間隔調用Cache.cleanUp()。ScheduledExecutorService能夠幫助你很好地實現這樣的定時調度。
刷新和回收不太同樣。正如LoadingCache.refresh(K)所聲明,刷新表示爲鍵加載新值,這個過程能夠是異步的。在刷新操做進行時,緩存仍然能夠向其餘線程返回舊值,而不像回收操做,讀緩存的線程必須等待新值加載完成。
若是刷新過程拋出異常,緩存將保留舊值,而異常會在記錄到日誌後被丟棄[swallowed]。
重載CacheLoader.reload(K, V)能夠擴展刷新時的行爲,這個方法容許開發者在計算新值時使用舊的值。
//有些鍵不須要刷新,而且咱們但願刷新是異步完成的 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .refreshAfterWrite(1, TimeUnit.MINUTES) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) { // no checked exception return getGraphFromDatabase(key); } public ListenableFuture<Key, Graph> reload(final Key key, Graph prevGraph) { if (neverNeedsRefresh(key)) { return Futures.immediateFuture(prevGraph); }else{ // asynchronous! ListenableFutureTask<Key, Graph> task=ListenableFutureTask.create(new Callable<Key, Graph>() { public Graph call() { return getGraphFromDatabase(key); } }); executor.execute(task); return task; } } });
CacheBuilder.recordStats()用來開啓Guava Cache的統計功能。統計打開後,Cache.stats()方法會返回CacheStats對象以提供以下統計信息:
此外,還有其餘不少統計信息。這些統計信息對於調整緩存設置是相當重要的,在性能要求高的應用中咱們建議密切關注這些數據。
asMap視圖提供了緩存的ConcurrentMap形式,但asMap視圖與緩存的交互須要注意: