一個比較現實的問題,咱們幾乎必定沒有足夠的內存緩存全部數據。你你必須決定:何時某個緩存項就不值得 保留了?Guava Cache 提供了三種基本的緩存回收方式:基於容量回收、定時回收和基於引用回收。java
若是要規定緩存項的數目不超過固定值,只需使用 CacheBuilder.maximumSize(long)。緩存將嘗試回收最近沒有使用或整體上不多使用的緩存項。——警告:在緩存項的數目達到限定值以前,緩存就可能進行回收操做——一般來講,這種狀況發生在緩存項的數目逼近限定值時。算法
另外,不一樣的緩存項有不一樣的「權重」(weights)——例如,若是你的緩存值,佔據徹底不一樣的內存空間,你可使用 CacheBuilder.weigher(Weigher)指定一個權重函數,而且用 CacheBuilder.maximumWeight(long)指定最大總重。在權重限定場景中,除了要注意回收也是在重量逼近限定值時就進行了,還要知道重量是在緩存建立時計算的,所以要考慮重量計算的複雜度。緩存
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder().maximumWeight(100000) .weigher(new Weigher<Key, Graph>() { public int weigh(Key k, Graph g) { return g.vertices().size(); } }).build(new CacheLoader<Key, Graph>() { public Graph load(Key key) { // no checked exception return createExpensiveGraph(key); } });
CacheBuilder 提供兩種定時回收的方法:異步
對定時回收進行測試時,不必定非得花費兩秒鐘去測試兩秒的過時。你可使用 Ticker 接口和 CacheBuilder.ticker(Ticker)方法在緩存中自定義一個時間源,而不是非得用系統時鐘。async
經過使用弱引用的鍵、或弱引用的值、或軟引用的值,Guava Cache 能夠把緩存設置爲容許垃圾回收:函數
任什麼時候候,你均可以顯式地清除緩存項,而不是等到它被回收:性能
經過 CacheBuilder.removalListener(RemovalListener),你能夠聲明一個監聽器,以便緩存項被移除時作一些額外操做。緩存項被移除時,RemovalListener 會獲取移除通知[RemovalNotification],其中包含移除緣由[RemovalCause]、鍵和值。測試
請注意,RemovalListener 拋出的任何異常都會在記錄到日誌後被丟棄[swallowed]。ui
CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection>() { public DatabaseConnection load(Key key) throws Exception { return openConnection(key); } }; RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() { public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) { DatabaseConnection conn = removal.getValue(); conn.close(); // tear down properly } }; return CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES).removalListener(removalListener) .build(loader);
警告:默認狀況下,監聽器方法是在移除緩存時同步調用的。由於緩存的維護和請求響應一般是同時進行的,代價高昂的監聽器方法在同步模式下會拖慢正常的緩存請求。在這種狀況下,你可使用 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) { // 沒有檢查異常 return getGraphFromDatabase(key); } public ListenableFuture<Key, Graph> reload(final Key key, Graph prevGraph) { if (neverNeedsRefresh(key)) { return Futures.immediateFuture(prevGraph); } else { // 異步的 ListenableFutureTask<Key, Graph> task = ListenableFutureTask .create(new Callable<Key, Graph>() { public Graph call() { return getGraphFromDatabase(key); } }); executor.execute(task); return task; } } });
CacheBuilder.refreshAfterWrite(long, TimeUnit)能夠爲緩存增長自動定時刷新功能。和 expireAfterWrite相反,refreshAfterWrite 經過定時刷新可讓緩存項保持可用,但請注意:緩存項只有在被檢索時纔會真正刷新(若是 CacheLoader.refresh 實現爲異步,那麼檢索不會被刷新拖慢)。所以,若是你在緩存上同時聲明 expireAfterWrite 和 refreshAfterWrite,緩存並不會由於刷新盲目地定時重置,若是緩存項沒有被檢索,那刷新就不會真的發生,緩存項在過時時間後也變得能夠回收。
LRU(Least recently used,最近最少使用)是核心思想是基於「若是數據最近被訪問過,那未來被訪問的概率也更高」。
LRU緩存機制:https://www.jianshu.com/p/8bf1c8f0eea4