Caffeine的异步淘汰清理机制
在惰性删除实现机制这边,Caffeine做了一些改进优化以提升在并发场景下的性能表现。我们可以和Guava Cache的基于容量大小的淘汰处理做个对比。
当限制了Guava Cache
最大容量之后,有新的记录写入超过了总大小,会理解触发数据淘汰策略,然后腾出空间给新的记录写入。比如下面这段逻辑:
public static void main(String[] args) {
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1)
.removalListener(notification -> System.out.println(notification.getKey() + "被移除,原因:" + notification.getCause()))
.build();
cache.put("key1", "value1");
System.out.println("key1写入后,当前缓存内的keys:" + cache.asMap().keySet());
cache.put("key2", "value1");
System.out.println("key2写入后,当前缓存内的keys:" + cache.asMap().keySet());
}
其运行后的结果显示如下,可以很明显的看出,超出容量之后继续写入,会在写入前先执行缓存移除操作。
key1写入后,当前缓存内的keys:[key1]
key1被移除,原因:SIZE
key2写入后,当前缓存内的keys:[key2]
同样地,我们看下使用Caffeine
实现一个限制容量大小的缓存对象的处理表现,代码如下:
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1)
.removalListener((key, value, cause) -> System.out.println(key + "被移除,原因:" + cause))
.build();
cache.put("key1", "value1");
System.out.println("key1写入后,当前缓存内的keys:" + cache.asMap().keySet());
cache.put("key2", "value1");
System.out.println("key2写入后,当前缓存内的keys:" + cache.asMap().keySet());
}
运行这段代码,会发现Caffeine的容量限制功能似乎“失灵”了!从输出结果看并没有限制住:
key1写入后,当前缓存内的keys:[key1]
key2写入后,当前缓存内的keys:[key1, key2]
什么原因呢?
Caffeine
为了提升读写操作的并发效率而将数据淘汰清理操作改为了异步处理,而异步处理时会有微小的延时,由此导致了上述看到的容量控制“失灵”现象。为了证实这一点,我们对上述的测试代码稍作修改,打印下调用线程与数据淘汰清理线程的线程ID,并且最后添加一个sleep等待操作:
public static void main(String[] args) throws Exception {
System.out.println("当前主线程:" + Thread.currentThread().getId());
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1)
.removalListener((key, value, cause) ->
System.out.println("数据淘汰执行线程:" + Thread.currentThread().getId()
+ "," + key + "被移除,原因:" + cause))
.build();
cache.put("key1", "value1");
System.out.println("key1写入后,当前缓存内的keys:" + cache.asMap().keySe());
cache.put("key2", "value1");
Thread.sleep(1000L); // 等待一段时间时间,等待异步清理操作完成
System.out.println("key2写入后,当前缓存内的keys:" + cache.asMap().keySet());
}
再次执行上述测试代码,发现结果变的符合预期了,也可以看出Caffeine的确是另起了独立线程去执行数据淘汰操作的。
当前主线程:1
key1写入后,当前缓存内的keys:[key1]
数据淘汰执行线程:13,key1被移除,原因:SIZE
key2写入后,当前缓存内的keys:[key2]
深扒一下源码的实现,可以发现Caffeine
在读写操作时会使用独立线程池执行对应的清理任务&#x