guava cache 使用须知

最近在重构一套老代码,发现了使用 guava cache 的几个坑。这里记录一下

首先 guava cache 是一个带大小限制的LRU 的 concurrentMap

在Map 的基础上, 还支持限size, 限read, write expire, 以及 removeListener

然而, 使用以上提供的api 并不是没有成本的.

size, access, write expire 和 removalListener 的数据结构

内部每一个功能点都通过一个 Queue 来维护.

access, write , LRU 的机制 和 removalListener 都通过一个 链表实现的Queue 实现, 名字分别为 recencyQueue, accessQueue, writeQueue, removalNotificationQueue

Weak,Soft Reference 则直接使用了 jdk 的 ReferenceQueue.

size 则通过每一个segment自己的 int 维护,超过了则删除 上面的Queue

也就是说,如果啥功能都不启用,也需要一个Queue 用来维护 LRU。

但如果同时启用了 accessExpire 和 writeExpire 的话,则需要同时维护3个 Queue。

而 guava 为了节省额外的线程, 维护它们的工作分别被平摊在了 每一次 写操作 以及 每64次 读操作

内部被抽象为一个 cleanUp 函数

具体干活 和 时间复杂度

  1. 将16个被GC 的弱引用对象 (包括 key, value) 重新加入Cache,

    关键函数: drainReferenceQueues
    时间复杂度 O(16 x 2)

  2. 循环最近的访问记录 recencyQueue 的访问纪录, 通过修改 AccessQueue, 重构LRU

    关键函数: drainRecencyQueue
    时间复杂度 O(n)

  3. 清理过期的 AccessQueue, WriteQueue, 从链表头扫描, 直至第一个不过期位置

    关键函数: expireEntries
    时间复杂度 O(n x 2)

  4. 消费所有 removalNotifaction 并在用户线程依次执行绑定的 listener

    关键函数: processPendingNotifications
    时间复杂度 O(n)
    ```

需要注意的是, 如果使用 removeListener 的话,由于她的执行也是在每一次读写操作中,

如果干了过慢的事情,会影响到 实际使用时的 put get 操作,具体现象 就是会发生抖动

avatar

lelouchcr's blog