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,它会检测是不是已经过期,过期就删除它,所以过期时间到了之后你去访问这个 Key 会显示这个 Key 已经被删除,但是如果你不做任何操作,那么在 过期时间到了之后也许这个 还在内存中。

三、如何解决

想要保证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

你可能感兴趣的:(guava)