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
函数
具体干活 和 时间复杂度
将16个被GC 的
弱引用对象
(包括 key, value) 重新加入Cache,关键函数:
drainReferenceQueues
时间复杂度 O(16 x 2)循环最近的访问记录 recencyQueue 的访问纪录, 通过修改 AccessQueue, 重构LRU
关键函数:
drainRecencyQueue
时间复杂度 O(n)清理过期的 AccessQueue, WriteQueue, 从链表头扫描, 直至第一个不过期位置
关键函数:
expireEntries
时间复杂度 O(n x 2)消费所有 removalNotifaction 并在
用户线程
依次执行绑定的 listener关键函数:
processPendingNotifications
时间复杂度 O(n)
```
需要注意的是, 如果使用 removeListener 的话,由于她的执行也是在每一次读写操作中,
如果干了过慢的事情,会影响到 实际使用时的 put get 操作,具体现象 就是会发生抖动