题目
设计一个基于Java 8+的线程安全缓存,支持过期时间、自动刷新和缓存穿透防护
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
ConcurrentHashMap, CompletableFuture, 函数式编程, 缓存设计, 并发控制
快速回答
实现要点:
- 使用
ConcurrentHashMap作为缓存存储结构 - 通过
CompletableFuture实现异步刷新和并发控制 - 采用
AtomicReference管理缓存值的时间戳 - 使用函数式接口实现懒加载逻辑
- 添加空值缓存和互斥锁解决缓存穿透问题
核心需求分析
需要实现一个支持以下特性的缓存:
- 线程安全:高并发下数据一致性
- 过期时间:数据自动失效
- 自动刷新:过期前异步刷新数据
- 缓存穿透防护:防止恶意查询不存在的数据
实现方案
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class AdvancedCache<K, V> {
private final ConcurrentHashMap<K, CacheValue<V>> cache = new ConcurrentHashMap<>();
private final Duration expireAfterWrite;
private final Duration refreshAfterWrite;
private final CacheLoader<K, V> loader;
public AdvancedCache(Duration expireAfterWrite, Duration refreshAfterWrite, CacheLoader<K, V> loader) {
this.expireAfterWrite = expireAfterWrite;
this.refreshAfterWrite = refreshAfterWrite;
this.loader = loader;
}
public V get(K key) {
CacheValue<V> cacheValue = cache.compute(key, (k, current) -> {
if (current == null || isExpired(current)) {
return loadValue(k);
}
if (shouldRefresh(current)) {
asyncRefresh(k, current);
}
return current;
});
return cacheValue != null ? cacheValue.getValue() : null;
}
private CacheValue<V> loadValue(K key) {
try {
V value = loader.load(key);
// 防止缓存穿透:缓存空值
if (value == null) return CacheValue.nullValue(expireAfterWrite);
return new CacheValue<>(value, System.nanoTime());
} catch (Exception e) {
throw new CompletionException(e);
}
}
private void asyncRefresh(K key, CacheValue<V> current) {
CompletableFuture.runAsync(() -> {
// 使用CAS确保只有一个线程执行刷新
if (current.refreshFlag.compareAndSet(false, true)) {
try {
V newValue = loader.load(key);
cache.put(key, new CacheValue<>(newValue, System.nanoTime()));
} finally {
current.refreshFlag.set(false);
}
}
});
}
private boolean isExpired(CacheValue<V> cacheValue) {
return System.nanoTime() - cacheValue.timestamp > expireAfterWrite.toNanos();
}
private boolean shouldRefresh(CacheValue<V> cacheValue) {
return System.nanoTime() - cacheValue.timestamp > refreshAfterWrite.toNanos();
}
@FunctionalInterface
public interface CacheLoader<K, V> {
V load(K key) throws Exception;
}
private static class CacheValue<V> {
private final V value;
private final long timestamp;
final AtomicBoolean refreshFlag = new AtomicBoolean(false);
CacheValue(V value, long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
static <V> CacheValue<V> nullValue(Duration duration) {
return new CacheValue<>(null, System.nanoTime() - duration.toNanos() - 1);
}
V getValue() {
return value;
}
}
}关键设计原理
- 并发控制:
- 使用
ConcurrentHashMap.compute()原子方法保证单key操作的线程安全 - 通过
AtomicBoolean实现刷新操作的互斥锁
- 使用
- 过期与刷新:
expireAfterWrite:绝对过期时间(强制重新加载)refreshAfterWrite:相对刷新时间(异步更新数据)- 时间戳比对采用纳秒精度避免误差
- 缓存穿透防护:
- 对
loader.load()返回的null值进行特殊缓存 - 空值设置立即过期策略,防止永久存储无效数据
- 对
最佳实践
- 资源控制:为异步刷新操作配置专用线程池
- 异常处理:在
load()方法中添加降级逻辑(如返回旧值) - 性能优化:对热点key添加二级本地缓存
- 监控:添加命中率/加载时间统计
常见错误
- 死锁风险:在
compute()方法中嵌套调用get() - 缓存雪崩:相同过期时间导致集中失效 → 添加随机偏移量
- 内存泄漏:未清理无效条目 → 定期清理或使用软引用
- 更新丢失:直接修改缓存对象 → 返回不可变对象
扩展知识
- 响应式刷新:使用
CompletableFuture.supplyAsync()实现非阻塞数据加载 - 分布式扩展:结合Redis Pub/Sub实现多节点缓存同步
- 高级特性:
- 权重淘汰策略(仿Caffeine)
- 时间滑动窗口统计
- JCache(JSR107)标准兼容