guava RemovalListener失效分析

一、問題

使用Guava Cache來實現自動清理緩存中的過期數據,基本代碼如下:


import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import java.util.concurrent.TimeUnit;

public class TestGuavaCache {

 public static void main(String[] args){
      RemovalListener<String, String> removalListener = new RemovalListener<String, String>() {
          @Override
          public void onRemoval(RemovalNotification<String, String> removalNotification) {
              System.out.println(removalNotification.getKey() + " : " + removalNotification.getValue() + "   has been removed");
          }
      };

      Cache<String, String> cache = CacheBuilder
             .newBuilder()
             .expireAfterAccess(5, TimeUnit.SECONDS)
           // .expireAfterWrite(5, TimeUnit.SECONDS)
            .removalListener(removalListener)
            .build();

     cache.put("1", "1");
     cache.put("2", "2");
     cache.put("3", "3");
     cache.put("4", "4");
     cache.put("5", "5");


     System.out.println("get : " + cache.getIfPresent("1"));
     System.out.println("get : " + cache.getIfPresent("2"));
     System.out.println("get : " + cache.getIfPresent("3"));
     System.out.println("get : " + cache.getIfPresent("4"));
     System.out.println("get : " + cache.getIfPresent("5"));

     try {
         Thread.sleep(6000);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }

     System.out.println("get : " + cache.getIfPresent("1"));
     System.out.println("get : " + cache.getIfPresent("2"));
     System.out.println("get : " + cache.getIfPresent("3"));
     System.out.println("get : " + cache.getIfPresent("4"));
     System.out.println("get : " + cache.getIfPresent("5"));
     System.out.println("remain size : " + cache.size());
 }
}

運行結果如下:

get : 1
get : 2
get : 3
get : 4
get : 5
get : null
get : null
get : null
get : null
get : null
remain size : 0

從運行結果來看,到期之後,cache的size爲0,但是註冊的RemovalListener並沒有被調用,爲什麼呢?

二、原因

通過官方wiki:https://github.com/google/guava/wiki/CachesExplained ,我們找到的原因,具體如下:

    Caches built with CacheBuilder do not perform cleanup and evict values "automatically," 
or instantly after a value expires, or anything of the sort. Instead, it performs small amounts 
of maintenance during write operations, or during occasional read operations if writes are rare.

  The reason for this is as follows: if we wanted to perform Cache maintenance continuously, we would 
need to create a thread, and its operations would be competing with user operations for shared locks. 
Additionally, some environments restrict the creation of threads, which would make CacheBuilder 
unusable in that environment.

  Instead, we put the choice in your hands. If your cache is high-throughput, then you don't have to 
worry about performing cache maintenance to clean up expired entries and the like. If your cache 
does writes only rarely and you don't want cleanup to block cache reads, you may wish to create 
your own maintenance thread that calls Cache.cleanUp() at regular intervals.

  If you want to schedule regular cache maintenance for a cache which only rarely has writes, 
just schedule the maintenance using ScheduledExecutorService.

從上面這段說明中,我們知道一下幾點:

  1. 使用CacheBuilder構建的Cahe不會“自動”執行清理數據,或者在數據過期後,立即執行清除操作。相反,它在寫操作期間或偶爾讀操作期間執行少量維護(如果寫很少)。

  2. 這樣做的原因如下:
    如果我們想要連續地執行緩存維護,我們需要創建一個線程,它的操作將與共享鎖的用戶操作發生競爭。此外,一些環境限制了線程的創建,這會使CacheBuilder在該環境中不可用。

簡單來說,GuavaCache 並不保證在過期時間到了之後立刻刪除該 <Key,Value>,如果你此時去訪問了這個 Key,它會檢測是不是已經過期,過期就刪除它,所以過期時間到了之後你去訪問這個 Key 會顯示這個 Key 已經被刪除,但是如果你不做任何操作,那麼在 過期時間到了之後也許這個<Key,Value> 還在內存中。

三、如何解決

想要保證RemovalListener生效,可以顯式調用用cleanUp()或者invalidate(),比如:

	cache.cleanUp();
       //cache.invalidate("1");

運行結果如下:

get : 1
get : 2
get : 3
get : 4
get : 5
get : null
get : null
get : null
get : null
get : null
remain size : 0
1 : 1   has been removed
2 : 2   has been removed
3 : 3   has been removed
4 : 4   has been removed
5 : 5   has been removed
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章