🌟 引言
在软件开发过程中,缓存是提升系统性能的常用手段。对于基础场景,直接使用 Java集合框架(如Map/Set/List)即可满足需求。然而,当面对更复杂的缓存场景时:
- 需要支持多种过期策略(基于时间、访问频率等)
- 要求自动淘汰机制
- 需要线程安全等高级特性
自行实现这些功能往往复杂度较高。本文将介绍 Java 生态中成熟的两大主流本地缓存解决方案:Caffeine (新一代缓存之王)和Guava Cache(经典缓存方案)。
📊 核心维度对比
评估维度 | Caffeine | Guava Cache |
---|---|---|
性能 | ⚡ 读写吞吐量高5-10倍 | 🐢 中等性能 |
内存效率 | 🧠 更低内存占用(优化数据结构) | 📦 较高内存消耗 |
并发能力 | 🚀 无锁算法,百万级QPS | 🔒 分段锁,十万级QPS |
淘汰算法 | 🎯 TinyLFU + LRU 自适应 | ⏳ 标准LRU |
监控统计 | 📈 内置详细指标 | 📊 基础统计 |
JDK兼容性 | Java 8+ | Java 6+ |
社区活跃度 | 🌟 持续更新(2023年仍有新版本) | 🛑 维护模式(仅修复bug) |
🚀 Caffeine
Caffeine 是一个性能ISS(In-Space Sizing)的缓存框架,它使用无锁算法和分段锁机制,以更优的方式优化了缓存淘汰算法。Caffeine 的设计目标为极致性能,并针对一些常见的场景进行了优化。
🌟 特性
- 无锁算法和分段锁机制,以更优的方式优化了缓存淘汰算法。
- 高命中率,通过优化淘汰算法,Caffeine 显著提高缓存命中率。
- 更低内存开销,Caffeine 使用更小的内存结构,从而减少内存消耗。
- 线程安全,Caffeine 支持并发操作,保证线程安全。
🌟 如何使用
yaml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.0</version>
</dependency>
java
import com.github.benmanes.caffeine.cache.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;
public class CaffeineDemo {
public static void main(String[] args) {
basicUsageDemo();
loadingCacheDemo();
asyncLoadingCacheDemo();
evictionDemo();
statisticsDemo();
}
/**
* 基础缓存操作示例
*/
public static void basicUsageDemo() {
System.out.println("\n=== 1. 基础缓存操作 ===");
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS) // 写入5秒后过期
.maximumSize(100) // 最大100个条目
.build();
// 手动写入
cache.put("key1", "value1");
// 获取值(不存在返回null)
String value = cache.getIfPresent("key1");
System.out.println("获取key1: " + value); // 输出: value1
// 获取或计算(线程安全)
String value2 = cache.get("key2", k -> "computed-" + k);
System.out.println("获取key2: " + value2); // 输出: computed-key2
}
/**
* 自动加载缓存示例
*/
public static void loadingCacheDemo() {
System.out.println("\n=== 2. 自动加载缓存 ===");
LoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterAccess(3, TimeUnit.SECONDS) // 3秒未访问则过期
.maximumSize(10)
.build(key -> {
// 模拟从数据库加载
System.out.println("正在加载: " + key);
return "db-value-" + key;
});
// 自动触发加载函数
System.out.println(cache.get("user1001")); // 输出: db-value-user1001
System.out.println(cache.get("user1001")); // 第二次直接从缓存获取
}
/**
* 异步加载缓存示例
*/
public static void asyncLoadingCacheDemo() {
System.out.println("\n=== 3. 异步加载缓存 ===");
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(1000)
.buildAsync(key -> {
// 模拟异步加载
return CompletableFuture.supplyAsync(() -> {
System.out.println("异步加载: " + key);
return "async-value-" + key;
});
});
// 异步获取
cache.get("id123").thenAccept(value -> {
System.out.println("异步获取结果: " + value); // 输出: async-value-id123
});
}
/**
* 淘汰策略示例
*/
public static void evictionDemo() {
System.out.println("\n=== 4. 淘汰策略 ===");
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(3) // 测试用的小容量
.removalListener((key, value, cause) ->
System.out.printf("淘汰事件: key=%s, 原因=%s\n", key, cause))
.build();
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.put("k3", "v3");
cache.put("k4", "v4"); // 触发淘汰(LRU)
System.out.println("当前大小: " + cache.estimatedSize()); // 输出: 3
}
/**
* 统计功能示例
*/
public static void statisticsDemo() {
System.out.println("\n=== 5. 统计功能 ===");
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.recordStats() // 开启统计
.build();
cache.put("k1", "v1");
cache.getIfPresent("k1");
cache.getIfPresent("missingKey");
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate()); // 输出: 0.5
System.out.println("命中数: " + stats.hitCount()); // 输出: 1
System.out.println("未命中数: " + stats.missCount()); // 输出: 1
}
}
🚀 Guava Cache
Guava Cache 是 Google 官方提供的一个缓存框架,它提供了许多高级特性,如自动加载、统计、序列化、并发控制等。与 Caffeine 不同,Guava Cache 的设计目标为简单易用,并支持更多的高级特性。
🌟 特性
- 自动加载、统计、序列化、并发控制等高级特性。
- 更高的并发控制,Guava Cache 使用更复杂的并发控制机制,以更优的方式解决并发问题。
🌟 如何使用
yaml
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.8-jre</version>
</dependency>
java
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.*;
import io.vavr.collection.List;
public class GuavaCacheDemo {
public static void main(String[] args) throws ExecutionException {
basicUsageDemo();
loadingCacheDemo();
cacheRemovalListenerDemo();
cacheStatisticsDemo();
advancedEvictionDemo();
}
/**
* 基础缓存操作示例
*/
public static void basicUsageDemo() {
System.out.println("\n=== 1. 基础缓存操作 ===");
Cache<String, String> cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS) // 写入5秒后过期
.maximumSize(100) // 最大100个条目
.concurrencyLevel(4) // 并发级别
.build();
// 手动写入
cache.put("key1", "value1");
// 获取值(不存在返回null)
String value = cache.getIfPresent("key1");
System.out.println("获取key1: " + value); // 输出: value1
// 尝试获取不存在的key
String value2 = cache.getIfPresent("key2");
System.out.println("获取不存在的key2: " + value2); // 输出: null
}
/**
* 自动加载缓存示例
*/
public static void loadingCacheDemo() throws ExecutionException {
System.out.println("\n=== 2. 自动加载缓存 ===");
LoadingCache<String, String> cache = CacheBuilder.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS) // 3秒未访问则过期
.maximumSize(10).build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
// 模拟从数据库加载
System.out.println("正在加载: " + key);
return "db-value-" + key;
}
});
// 自动触发加载函数
System.out.println(cache.get("user1001")); // 输出: db-value-user1001
System.out.println(cache.get("user1001")); // 第二次直接从缓存获取
// 批量获取
System.out.println(cache.getAll(List.of("user1002", "user1003")));
}
/**
* 缓存淘汰监听器示例
*/
public static void cacheRemovalListenerDemo() {
System.out.println("\n=== 3. 淘汰监听器 ===");
RemovalListener<String, String> listener = notification -> {
System.out.printf("淘汰事件: key=%s, value=%s, 原因=%s\n", notification.getKey(), notification.getValue(),
notification.getCause());
};
Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(3) // 测试用的小容量
.removalListener(listener).build();
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.put("k3", "v3");
cache.put("k4", "v4"); // 触发淘汰(LRU)
cache.invalidate("k2"); // 手动触发淘汰
}
/**
* 缓存统计示例
*/
public static void cacheStatisticsDemo() {
System.out.println("\n=== 4. 缓存统计 ===");
Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(100).recordStats() // 开启统计
.build();
cache.put("k1", "v1");
cache.getIfPresent("k1");
cache.getIfPresent("missingKey");
CacheStats stats = cache.stats();
System.out.println("命中率: " + stats.hitRate()); // 输出: 0.5
System.out.println("命中数: " + stats.hitCount()); // 输出: 1
System.out.println("未命中数: " + stats.missCount()); // 输出: 1
System.out.println("加载成功数: " + stats.loadSuccessCount());
}
/**
* 高级淘汰策略示例
*/
public static void advancedEvictionDemo() {
System.out.println("\n=== 5. 高级淘汰策略 ===");
Cache<String, String> cache = CacheBuilder.newBuilder()
// 基于权重的淘汰(假设不同value占用不同空间)
.maximumWeight(1000).weigher((String key, String value) -> value.length())
// 弱引用key和value(适合缓存大对象)
.weakKeys().weakValues()
// 定期维护(减少并发开销)
.concurrencyLevel(8).build();
cache.put("long", "这是一个很长的字符串值");
cache.put("short", "小");
System.out.println("当前大小: " + cache.size());
}
}
🎉 结论
对于大多数现代 Java 应用,Caffeine 无疑是更优选择,其卓越的性能表现和更低的内存开销使其成为新项目的首选。而 Guava Cache 则更适合已有 Guava 生态的遗留系统,或者需要特定功能(如 CacheLoader 深度集成)的场景。
终极建议: 新项目直接采用 Caffeine,老项目若无性能瓶颈可继续使用 Guava Cache,在遇到性能问题时再考虑迁移。两者 API 相似,迁移成本较低。