说起Guava Cache,很多人都不会陌生,它是Google Guava工具包中的一个非常方便易用的本地化缓存实现,基于LRU算法实现,支持多种缓存过期策略。由于Guava的大量使用,Guava Cache也得到了大量的应用。但是,Guava Cache的性能一定是最好的吗?也许,曾经,它的性能是非常不错的。但所谓长江后浪推前浪,总会有更加优秀的技术出现。今天,我就来介绍一个比Guava Cache性能更高的缓存框架:Caffeine。
Tips: Spring5(SpringBoot2)开始用Caffeine取代guava.详见官方信息SPR-13797
https://jira.spring.io/browse/SPR-13797
由图可以看出,Caffeine不论读还是写的效率都远高于其他缓存。
这里只列出部分性能比较,详细请看官方官方 https://github.com/ben-manes/caffeine/wiki/Benchmarks
我们需要在 pom.xml 中添加 caffeine 依赖:
版本问题参考https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine
<dependency>
<groupId>com.github.ben-manes.caffeinegroupId>
<artifactId>caffeineartifactId>
<version>2.7.0version>
dependency>
// 1、最简单
Cache<String, Object> cache = Caffeine.newBuilder()
.build();
// 2、真实使用过程中我们需要自己配置参数。这里只列举部分,具体请看下面列表
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(2)//初始大小
.maximumSize(2)//最大数量
.expireAfterWrite(3, TimeUnit.SECONDS)//过期时间
.build();
Caffeine 为我们提供了三种填充策略:
手动、同步和异步
很简单的
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.build();
cache.put("hello", "world");
System.out.println(cache.getIfPresent("hello"));
}
Cache<String, String> cache = Caffeine.newBuilder()
.build();
// 1.如果缓存中能查到,则直接返回
// 2.如果查不到,则从我们自定义的getValue方法获取数据,并加入到缓存中
cache.get("hello", new Function<String, String>() {
@Override
public String apply(String k) {
return getValue(k);
}
});
System.out.println(cache.getIfPresent("hello"));
}
// 缓存中找不到,则会进入这个方法。一般是从数据库获取内容
private static String getValue(String k) {
return k + ":value";
// 这种写法可以简化成下面Lambda表达式
cache.get(“hello”, new Function() {
@Override
public String apply(String k) {
return getValue(k);
}
});
// 可以简写为
cache.get(“hello”, k -> getValue(k));
和上面方法一样,只不过这个是在新建对象的时候添加
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public String load(String k) {
return getValue(k);
}
});
// 同样可简化为下面这样
LoadingCache<String, String> loadingCache2 = Caffeine.newBuilder()
.build(k -> getValue(k));
Caffeine提供三类驱逐策略:
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(3)
.build();
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
cache.put("key4", "value4");
cache.put("key5", "value5");
cache.cleanUp();
System.out.println(cache.getIfPresent("key1"));
System.out.println(cache.getIfPresent("key2"));
System.out.println(cache.getIfPresent("key3"));
System.out.println(cache.getIfPresent("key4"));
System.out.println(cache.getIfPresent("key5"));
输出结果
null
value2
null
value4
value5
1、淘汰2个
2、淘汰并不是按照先后顺序,内部有自己的算法
Caffeine提供了三种定时驱逐策略:
缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)。
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build();
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
cache.put("key4", "value4");
cache.put("key5", "value5");
System.out.println(cache.getIfPresent("key1"));
System.out.println(cache.getIfPresent("key2"));
Thread.sleep(3*1000);
System.out.println(cache.getIfPresent("key3"));
System.out.println(cache.getIfPresent("key4"));
System.out.println(cache.getIfPresent("key5"));
结果
value1
value2
null
null
null
例子2
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build();
cache.put("key1", "value1");
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("key1"));
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("key1"));
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("key1"));
结果
value1
value1
null
Access就是读和写
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterAccess(3, TimeUnit.SECONDS)
.build();
cache.put("key1", "value1");
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("key1"));
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("key1"));
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("key1"));
Thread.sleep(3*1000);
System.out.println(cache.getIfPresent("key1"));
结果
value1
value1
value1
null
读和写都没有的情况下,3秒后才过期
Cache<String, Object> cache = Caffeine.newBuilder()
.weakValues()
.build();
Object value1 = new Object();
Object value2 = new Object();
cache.put("key1", value1);
cache.put("key2", value2);
value2 = new Object(); // 原对象不再有强引用
System.gc();
System.out.println(cache.getIfPresent("key1"));
System.out.println(cache.getIfPresent("key2"));
结果
java.lang.Object@7a4f0f29
null
解释:当给value2引用赋值一个新的对象之后,就不再有任何一个强引用指向原对象。System.gc()触发垃圾回收后,原对象就被清除了。
简单回顾下Java中的四种引用
Java4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | GC运行后终止 |
虚引用 | Unknown | Unknown | Unknown |
除了通过上面的缓存淘汰策略删除缓存,我们还可以手动的删除
// 1、指定key删除
cache.invalidate("key1");
// 2、批量指定key删除
List<String> list = new ArrayList<>();
list.add("key1");
list.add("key2");
cache.invalidateAll(list);//批量清除list中全部key对应的记录
// 3、删除全部
cache.invalidateAll();
可以为缓存对象添加一个移除监听器,这样当有记录被删除时可以感知到这个事件。
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterAccess(3, TimeUnit.SECONDS)
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
System.out.println("key:" + key + ",value:" + value + ",删除原因:" + cause);
}
})
.expireAfterWrite(1, TimeUnit.SECONDS)
.build();
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.invalidate("key1");
Thread.sleep(2 * 1000);
cache.cleanUp();
结果
key:key1,value:value1,删除原因:EXPLICIT
key:key2,value:value2,删除原因:EXPIRED
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(3)
.recordStats()
.build();
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
cache.put("key4", "value4");
cache.getIfPresent("key1");
cache.getIfPresent("key2");
cache.getIfPresent("key3");
cache.getIfPresent("key4");
cache.getIfPresent("key5");
cache.getIfPresent("key6");
System.out.println(cache.stats());
结果
CacheStats{hitCount=4, missCount=2, loadSuccessCount=0, loadFailureCount=0, totalLoadTime=0, evictionCount=0, evictionWeight=0}
除了结果输出的内容,CacheStats还可以获取如下数据。
http://oopsguy.com/2017/10/25/java-caching-caffeine/
https://juejin.im/post/5b8df63c6fb9a019e04ebaf4
https://www.jianshu.com/p/9a80c662dac4
https://www.sohu.com/a/235729991_100109711
https://www.cnblogs.com/yueshutong/p/9381540.html
https://blog.csdn.net/qq_38974634/article/details/80650810
https://blog.csdn.net/qq_32867467/article/details/82944506
https://blog.csdn.net/grafx/article/details/80462628
http://ifeve.com/google-guava-cachesexplained/