清除什么时候发生?

也许这个问题有点奇怪,如果设置的存活时间为一分钟,难道不是一分钟后这个key就会立即清除掉吗?我们来分析一下如果要实现这个功能,那Cache中就必须存在线程来进行周期性地检查、清除等工作,很多cache如redis、ehcache都是这样实现的。
但在GuavaCache中,并不存在任何线程!它实现机制是在写操作时顺带做少量的维护工作(如清除),偶尔在读操作时做(如果写操作实在太少的话),也就是说在使用的是调用线程,参考如下示例:

代码

  1. public class CacheService {
  2. static Cache<Integer, String> cache = CacheBuilder.newBuilder()
  3. .expireAfterWrite(5, TimeUnit.SECONDS)
  4. .build();
  5. public static void main(String[] args) throws Exception {
  6. new Thread() { //monitor
  7. public void run() {
  8. while(true) {
  9. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  10. System.out.println(sdf.format(new Date()) +" size: "+cache.size());
  11. try {
  12. Thread.sleep(2000);
  13. } catch (InterruptedException e) {
  14. }
  15. }
  16. };
  17. }.start();
  18. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
  19. cache.put(1, "Hi");
  20. System.out.println("write key:1 ,value:"+cache.getIfPresent(1));
  21. Thread.sleep(10000);
  22. // when write ,key:1 clear
  23. cache.put(2, "bbb");
  24. System.out.println("write key:2 ,value:"+cache.getIfPresent(2));
  25. Thread.sleep(10000);
  26. // when read other key ,key:2 do not clear
  27. System.out.println(sdf.format(new Date())
  28. +" after write, key:1 ,value:"+cache.getIfPresent(1));
  29. Thread.sleep(2000);
  30. // when read same key ,key:2 clear
  31. System.out.println(sdf.format(new Date())
  32. +" final, key:2 ,value:"+cache.getIfPresent(2));
  33. }
  34. }

控制台输出


  1. 00:34:17 size: 0
  2. write key:1 ,value:Hi
  3. 00:34:19 size: 1
  4. 00:34:21 size: 1
  5. 00:34:23 size: 1
  6. 00:34:25 size: 1
  7. write key:2 ,value:bbb
  8. 00:34:27 size: 1
  9. 00:34:29 size: 1
  10. 00:34:31 size: 1
  11. 00:34:33 size: 1
  12. 00:34:35 size: 1
  13. 00:34:37 after write, key:1 ,value:null
  14. 00:34:37 size: 1
  15. 00:34:39 final, key:2 ,value:null
  16. 00:34:39 size: 0

通过分析发现:
(1)缓存项<1,"Hi">的存活时间是5秒,但经过5秒后并没有被清除,因为还是size=1
(2)发生写操作cache.put(2, “bbb”)后,缓存项<1,"Hi">被清除,因为size=1,而不是size=2
(3)发生读操作cache.getIfPresent(1)后,缓存项<2,"bbb">没有被清除,因为还是size=1,看来读操作确实不一定会发生清除
(4)发生读操作cache.getIfPresent(2)后,缓存项<2,"bbb">被清除,因为读的key就是2

这在GuavaCache被称为“延迟删除”,即删除总是发生得比较“晚”,这也是GuavaCache不同于其他Cache的地方!这种实现方式的问题:缓存会可能会存活比较长的时间,一直占用着内存。如果使用了复杂的清除策略如基于容量的清除,还可能会占用着线程而导致响应时间变长。但优点也是显而易见的,没有启动线程,不管是实现,还是使用起来都让人觉得简单(轻量)。
如果你还是希望尽可能的降低延迟,可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp(),ScheduledExecutorService可以帮助你很好地实现这样的定时调度。不过这种方式依然没办法百分百的确定一定是自己的维护线程“命中”了维护的工作。

总结

请一定要记住GuavaCache的实现代码中没有启动任何线程!!Cache中的所有维护操作,包括清除缓存、写入缓存等,都是通过调用线程来操作的。这在需要低延迟服务场景中使用时尤其需要关注,可能会在某个调用的响应时间突然变大。
GuavaCache毕竟是一款面向本地缓存的,轻量级的Cache,适合缓存少量数据。如果你想缓存上千万数据,可以为每个key设置不同的存活时间,并且高性能,那并不适合使用GuavaCache。