侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计一个基于Java 8+的线程安全缓存,支持过期时间、自动刷新和缓存穿透防护

2025-12-13 / 0 评论 / 4 阅读

题目

设计一个基于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)标准兼容