Caffeine 深度解析
一、核心定位与简介
1.1 什么是 Caffeine
Caffeine 是一款基于 Java 8 开发的高性能本地缓存框架,是目前 Java 生态中性能最优的本地缓存实现,由 Guava Cache 原核心作者参与研发,属于 Guava Cache 的升级迭代版本。
Caffeine 吸收了 Guava Cache 的核心设计思想,结合 Java 8 的 Lambda 表达式、CompletableFuture 异步特性做了深度优化,同时采用当前业界最优的缓存淘汰算法,是 Java 项目中本地缓存的首选方案。
1.2 核心优势
- 极致的读写性能:读操作性能接近原生 ConcurrentHashMap,写操作性能远超同类框架
- 超高的缓存命中率:自研 W-TinyLFU 淘汰算法,命中率远高于 Guava Cache、Ehcache 等
- 轻量级无依赖:核心包体积小巧,无第三方依赖,兼容所有 Java 8+ 版本项目
- 丰富的功能特性:支持多种加载模式、过期策略、异步操作、缓存统计等
- 规范兼容:完美兼容 JSR-107 缓存规范,适配标准化缓存开发
- 内存友好:支持基于 Java 引用的内存回收策略,避免内存溢出
二、核心底层原理
2.1 核心缓存淘汰算法:W-TinyLFU
Caffeine 超高命中率的核心是 W-TinyLFU(Window-TinyLFU) 算法,这是一种复合型缓存淘汰策略,融合经典算法优势并解决其痛点:
- 核心设计思想:结合 LFU(最少频率使用)的高频访问优先 + LRU(最近最少使用)的近期访问优先,兼顾「访问频率」与「访问时效性」
- 解决 LRU 痛点:避免「缓存污染」(低频大流量请求挤出高频数据)
- 解决 LFU 痛点:避免「冷启动」(新热点数据无法缓存)和「数据僵化」(历史高频数据长期占用缓存)
- 核心优化:通过窗口缓存预热新数据访问频率,结合频率过滤机制,留存高频数据、纳入新热点、剔除低频数据
2.2 核心执行机制
- 存储实现:基于分段式并发哈希表,与 ConcurrentHashMap 分段锁思路一致,读写无锁化设计,降低线程竞争损耗
- 过期清理:采用「惰性删除 + 定时主动清理」组合机制,兼顾性能与内存占用
- 加载逻辑:基于 Java 8 函数式编程实现,支持同步/异步模式,异步操作依托
CompletableFuture提升并发效率
三、核心缓存策略
3.1 缓存数据淘汰策略
Caffeine 淘汰策略分两类,自动执行无需手动干预:
- 被动淘汰:缓存容量达预设最大值时,触发 W-TinyLFU 算法驱逐「价值最低」数据
- 主动淘汰:缓存数据满足过期规则时自动移除,触发时机包括惰性检查、定时清理
3.2 三大核心过期策略
3.2.1 基于大小的过期(容量驱逐)
设置缓存最大容量上限,达到上限时触发 W-TinyLFU 淘汰,支持最大条目数/自定义权重配置,是防止内存溢出的核心配置:
- maximumSize(最大条目数):相当于给缓存设置「最多能放多少个东西」,不管每个东西大小,只要数量到上限就踢掉最没用的。
比如设置maximumSize(1000),缓存里放了1000个键值对(哪怕其中有1KB的小数据,也有100MB的大数据),就会触发驱逐。 - 权重(maximumWeight + weigher):相当于给缓存设置「最多能装多少容量的东西」,每个东西先算「重量」,总重量到上限就踢掉最没用的。
比如设置maximumWeight(10240)(总权重10KB),给每个字符串缓存项按长度算权重(weigher((k,v)->v.length())),当所有字符串总长度≥10KB时,触发驱逐。
配置示例:
// 配置最大条目数
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000) // 最大缓存条目数
.build();
// 配置自定义权重
LoadingCache<String, String> weightCache = Caffeine.newBuilder()
.maximumWeight(10240) // 总权重上限
.weigher((key, value) -> value.length()) // 自定义每个缓存项的权重
.build(key -> loadData(key));
3.2.2 基于时间的过期(时效驱逐)
业务开发最常用策略,支持两种细分规则:
- 访问后过期(TTL):最后一次访问开始计时,超时过期,适用于热点时效性数据(如用户会话)
- 写入后过期(TTI):首次写入开始计时,超时过期,适用于固定更新频率数据(如配置信息)
配置示例:
LoadingCache<String, String> timeCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(5, TimeUnit.MINUTES) // TTL:访问后5分钟过期
.expireAfterWrite(10, TimeUnit.MINUTES) // TTI:写入后10分钟过期
.build(key -> loadData(key));
3.2.3 基于引用的过期(内存感知驱逐)
基于 Java 引用类型实现内存级回收,对接 JVM 垃圾回收机制:
- 软引用策略:JVM 内存不足时回收缓存,避免内存溢出
- 弱引用策略:键对象无引用时立即回收缓存,适配对象生命周期绑定场景
配置示例:
// 软引用值
Cache<String, String> softCache = Caffeine.newBuilder()
.softValues()
.build();
// 弱引用键
Cache<String, String> weakCache = Caffeine.newBuilder()
.weakKeys()
.build();
四、核心缓存工作模式
核心特性:缓存增删改查完全手动控制,缓存缺失时框架不自动处理
适用场景:少量固定数据缓存、需精准控制更新时机的场景
示例代码:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Cache;
import java.util.concurrent.TimeUnit;
public class ManualLoadExample {
public static void main(String[] args) {
// 构建手动加载缓存
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
// 手动存入
cache.put("user:1001", "张三");
// 手动获取
String userName = cache.getIfPresent("user:1001");
// 手动删除
cache.invalidate("user:1001");
}
}
4.2 自动加载模式
核心特性:配置加载逻辑,缓存缺失时框架自动加载并写入缓存
适用场景:绝大多数查询类业务,是 Caffeine 最常用模式
示例代码:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class AutoLoadExample {
public static void main(String[] args) {
// 构建自动加载缓存
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(key -> loadDataFromDB(key)); // 缓存缺失时自动执行
// 获取缓存(缺失则自动加载)
String productName = loadingCache.get("product:2001");
}
// 模拟从数据库加载数据
private static String loadDataFromDB(String key) {
System.out.println("从数据库加载数据:" + key);
return "商品_" + key.split(":")[1];
}
}
4.3 异步手动加载模式
核心特性:基于 CompletableFuture 实现异步非阻塞操作,提升高并发响应效率
适用场景:高并发、对响应时间要求严苛的场景
示例代码:
import com.github.benmanes.caffeine.cache.AsyncCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsyncManualLoadExample {
public static void main(String[] args) throws Exception {
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.buildAsync();
// 异步存入
asyncCache.put("order:3001", CompletableFuture.supplyAsync(() -> "订单_3001"));
// 异步获取
CompletableFuture<String> orderFuture = asyncCache.getIfPresent("order:3001");
String orderInfo = orderFuture.get(); // 阻塞获取结果(实际业务中建议链式调用)
}
}
4.4 异步自动加载模式
核心特性:融合自动加载与异步加载优势,终极高性能模式,缓存缺失时异步加载,主线程不阻塞
适用场景:高并发+高频查询的核心业务场景
示例代码:
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsyncAutoLoadExample {
public static void main(String[] args) throws Exception {
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.buildAsync(key -> CompletableFuture.supplyAsync(() -> loadDataFromDBAsync(key)));
// 异步获取,主线程不阻塞
CompletableFuture<String> dataFuture = asyncLoadingCache.get("goods:4001");
// 链式处理结果(非阻塞)
dataFuture.thenAccept(data -> System.out.println("加载结果:" + data));
}
private static String loadDataFromDBAsync(String key) {
try {
// 模拟耗时加载
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "异步加载_" + key;
}
}
五、核心功能特性
5.1 高性能并发控制
- 底层无锁化并发设计,支持数万级并发读写,线程安全无锁竞争损耗
- 支持自定义并发级别,适配不同系统负载,兼顾性能与内存占用
5.2 缓存刷新机制
缓存刷新是未过期时异步更新数据(与过期重新加载不同),刷新期间返回旧值,保证响应效率与数据最终一致性,刷新机制是惰性刷新,只有key被访问才会被触发:
LoadingCache<String, String> refreshCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES)
// 每次命中检查key有效时间<=30-5分钟时异步刷新一次
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(key -> loadDataFromDB(key));
5.3 缓存淘汰监听器
监听缓存淘汰/过期/删除事件,支持同步/异步回调,用于统计、日志、资源释放:
Cache<String, String> listenerCache = Caffeine.newBuilder()
.maximumSize(1000)
.removalListener((key, value, cause) -> {
// cause枚举:EXPLICIT(手动删除)、EXPIRED(过期)、SIZE(容量驱逐)
System.out.printf("缓存移除 - 键:%s,原因:%s%n", key, cause.name());
})
.build();
5.4 完整的缓存统计功能
开启后可获取命中率、加载时间、驱逐数等核心指标,用于性能调优:
LoadingCache<String, String> statsCache = Caffeine.newBuilder()
.maximumSize(1000)
.recordStats() // 开启统计
.build(key -> loadDataFromDB(key));
// 获取核心统计指标
System.out.println("命中率:" + statsCache.stats().hitRate());
System.out.println("驱逐次数:" + statsCache.stats().evictionCount());
System.out.println("平均加载时间:" + statsCache.stats().averageLoadTime() + "ns");
5.5 高并发场景配置
Caffeine 底层基于分段式哈希表实现(类似 ConcurrentHashMap),并发级别对应「哈希表的分段数」,Caffeine 提供 concurrencyLevel(int) 方法显式设置并发级别,默认是16(仅在以下特殊场景需要调整),如果缓存加载逻辑使用自定义线程池,建议并发级别与线程池大小匹配,代码示例如下:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CaffeineConcurrencyWithPool {
public static void main(String[] args) {
// 自定义线程池(8 核心)
var executor = Executors.newFixedThreadPool(8);
// 并发级别设为 8(与线程池大小一致,且为 2 的幂次方)
LoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 设置并发级别为8
.concurrencyLevel(8)
.executor(executor) // 绑定自定义线程池
.build(key -> loadDataFromDB(key));
}
private static String loadDataFromDB(String key) {
return "data_" + key;
}
}
5.5 其他核心特性
- 禁止缓存空值:避免缓存穿透,可配置过滤空值/无效值
- 缓存预热:项目启动时加载热点数据,避免初期缓存缺失
- 自定义键值配置:支持自定义 equals/hashCode、序列化规则,适配自定义对象键
六、与主流本地缓存框架对比
| 维度 | Caffeine | Guava Cache | Ehcache 3.x | ConcurrentHashMap |
|---|---|---|---|---|
| 读写性能 | 极致(最优) | 良好 | 一般 | 读优写一般 |
| 缓存命中率 | 最高 | 中等 | 中等 | 无淘汰策略 |
| 功能完备性 | 丰富 | 基础 | 极丰富(企业级) | 极简 |
| 轻量化 | 最优 | 优秀 | 较重 | 最优 |
| 分布式支持 | 不支持(本地) | 不支持(本地) | 支持 | 不支持 |
| 持久化支持 | 不支持 | 不支持 | 支持 | 不支持 |
核心结论
- 纯内存本地缓存:优先选 Caffeine
- 需持久化/分布式:选 Ehcache 3.x
- Java 7 项目:选 Guava Cache
- 简单临时存储:选 ConcurrentHashMap
七、最佳实践与避坑指南
7.1 核心配置优化原则
- 容量:设置为热点数据量的 1.5-2 倍,避免频繁驱逐/内存浪费
- 过期时间:热点数据用 TTL(访问后过期),静态数据用 TTI(写入后过期),兼顾命中率与一致性
- 引用策略:内存敏感业务用软引用,普通业务用默认强引用
- 统计:必开统计功能,通过指标优化配置
- 并发级别:使用默认值可满足99%的高并发场景
7.2 业务场景最佳适配
- 普通查询:自动加载模式 + TTL 过期
- 高并发核心业务:异步自动加载 + 容量驱逐 + TTL
- 内存敏感业务:软引用 + 容量驱逐
- 高频更新业务:手动加载 + TTI 过期
7.3 核心避坑指南
- 混淆刷新与过期:刷新是异步更新,过期是重新加载,按需选择
- 盲目设置过期时间:静态数据无需过期,动态数据必须设置
- 缓存空值:无特殊需求时禁止缓存空值,避免内存浪费
- 可变对象做键:保证键不可变或重写 equals/hashCode
- 过度依赖缓存:缓存仅为性能优化手段,需保证缓存缺失时业务正常
- 忽略统计:通过命中率调整配置(命中率<80%需优化容量/过期时间)
八、适用与不适用场景
8.1 核心适用场景
- 高频访问的本地热点数据缓存(配置、用户信息、商品详情)
- 分布式缓存(Redis)的一级缓存,降低分布式缓存压力
- 高并发无状态缓存(限流、熔断规则)
- 内存敏感型业务缓存
- 异步编程的缓存需求
8.2 不适用场景
- 需要持久化存储的缓存(选 Ehcache/Redis)
- 需要分布式一致性的缓存(选 Redis/Memcached)
- 超大容量缓存存储(选分布式缓存)
- 跨 JVM 共享缓存数据(选分布式缓存)
九、总结
9.1 核心总结
- Caffeine 是 Java 8+ 最优本地缓存框架,W-TinyLFU 算法保证超高命中率,性能极致
- 核心优势是高性能、高命中率、轻量级、功能完备,适配所有本地缓存需求
- 四种工作模式 + 三种过期策略可灵活组合,自动/异步加载是核心亮点
- 「Caffeine + Redis」双层缓存架构是微服务最优缓存方案
9.2 最终建议
- Java 本地缓存优先选择 Caffeine,远超同类框架
- 配置遵循「合理容量、按需过期、开启统计」原则,持续优化
- 明确缓存定位为「性能优化手段」,保证业务容错能力
- 避开常见坑点,最大化发挥 Caffeine 性能优势
Caffeine 是 Java 本地缓存的事实标准,掌握其核心特性与最佳实践,能显著提升项目并发性能与开发效率,是 Java 开发人员必备技术栈。
更多推荐

所有评论(0)