当前位置: 首页 > news >正文

Guava Cache的refreshAfterWrite机制

Guava Cache 的 refreshAfterWrite 机制在源码中通过 惰性刷新调度细粒度锁控制 实现,核心逻辑集中在 LocalCache 类的 Segment 结构中。以下是关键源码解析:


一、核心数据结构

1. Segment

继承自 ReentrantLock:每个 Segment 独立加锁,控制并发访问。
内部数据结构
AtomicReferenceArray<ReferenceEntry<K, V>> table:存储缓存条目的哈希表。

1. AtomicReferenceArray

定义:Java 并发包 java.util.concurrent.atomic 中的类,提供 原子操作的引用数组,支持无锁化的 CAS(Compare-And-Swap)操作。
特性
线程安全:通过 CAS 保证对数组元素的原子性更新,避免传统锁的开销。
固定长度:创建时指定容量,不可动态扩容(Guava Cache 通过分段机制解决容量扩展问题)。

2. ReferenceEntry<K, V>

角色:缓存条目的数据结构,存储键值对(KV)及元数据(如访问时间、过期时间、哈希值等)。
链表结构:通过 next 指针形成链表,解决哈希冲突(类似 ConcurrentHashMap 的链地址法)。

• 5 个队列(访问队列、写入队列等):管理缓存项的回收策略。


二、触发刷新的关键方法

1. get 方法入口
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
    // 1. 根据 hash 获取 Segment
    Segment<K, V> segment = segmentFor(hash);
    // 2. 调用 Segment 的 get 方法
    return segment.get(key, hash, loader);
}

关键逻辑:通过 hash 定位到具体的 Segment,由 Segment 处理并发和刷新。


2. Segment#get 方法
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
    // 1. 尝试获取缓存条目
    ReferenceEntry<K, V> e = getEntry(key, hash);
    V value = getLiveValue(e); // 检查是否过期
    
    if (value != null) {
        // 2. 检查是否需要刷新
        if (scheduleRefresh(e, key, hash, value, now, loader)) {
            return value; // 返回旧值,异步刷新
        }
        return value;
    }
    
    // 3. 缓存未命中,加锁加载新值
    return lockedGetOrLoad(key, hash, loader);
}

核心逻辑
步骤 1:通过 getEntry 获取缓存条目。
步骤 2:若条目存在且未过期,调用 scheduleRefresh 判断是否触发异步刷新。
步骤 3:若条目不存在或过期,加锁加载新值(阻塞式加载)。


3. scheduleRefresh 方法
boolean scheduleRefresh(ReferenceEntry<K, V> entry, K key, int hash, V oldValue, long now, CacheLoader<? super K, V> loader) {
    // 1. 检查是否配置了 refreshAfterWrite
    if (!map.refreshAfterWrite()) return false;
    
    // 2. 计算是否超过刷新时间
    long refreshTime = entry.getWriteTime() + map.refreshNanos;
    if (now >= refreshTime) {
        // 3. 触发异步刷新
        refresh(key, hash, loader, false);
        return true;
    }
    return false;
}

关键逻辑
• 判断是否超过 refreshAfterWrite 时间。
• 若超时,调用 refresh 方法异步加载新值。


4. refresh 方法
V refresh(K key, int hash, CacheLoader<? super K, V> loader, boolean checkTime) {
    // 1. 加锁,确保只有一个线程执行加载
    Lock lock = segment.lock();
    try {
        // 2. 检查是否已被其他线程加载
        ReferenceEntry<K, V> entry = getEntry(key, hash);
        if (entry == null) return null;
        
        // 3. 调用 CacheLoader.reload 异步加载
        ListenableFuture<V> future = loader.reload(key, getLiveValue(entry));
        if (future != null) {
            // 4. 提交到线程池执行
            ListenableFutureTask<V> task = ListenableFutureTask.create(future);
            executor.execute(task);
            return task.get(); // 阻塞等待结果(仅当前线程)
        }
    } finally {
        lock.unlock();
    }
    return null;
}

关键逻辑
加锁:通过 Segment 锁保证同一 Key 的刷新串行化。
异步加载:调用 CacheLoader.reload 返回 ListenableFuture,提交到独立线程池执行。
返回旧值:当前线程直接返回旧值,其他线程不受影响。


三、异步刷新的线程模型

1. 线程池配置

默认行为:使用 MoreExecutors.directExecutor()(同步执行)。
推荐配置:通过 CacheBuilder.refreshAfterWrite 指定独立线程池:

LoadingCache<Key, Value> cache = CacheBuilder.newBuilder()
    .refreshAfterWrite(1, TimeUnit.SECONDS)
    .build(new CacheLoader<Key, Value>() {
        @Override
        public ListenableFuture<Value> reload(Key key, Value oldValue) {
            return executor.submit(() -> loadNewValue(key)); // 异步加载
        }
    });
2. ListenableFuture 机制

非阻塞等待:主线程不阻塞,其他请求直接返回旧值。
异常处理:若异步加载失败,后续请求会触发重试。


四、锁与并发控制

1. 细粒度锁

锁粒度:仅对当前 Segment 加锁,而非全局锁。
锁竞争:不同 Key 的刷新操作在不同 Segment 中并行执行。

2. CAS 操作

原子性更新:通过 AtomicReferenceArray 保证缓存条目的原子性更新。


五、expireAfterWrite 的差异

逻辑refreshAfterWrite 源码实现expireAfterWrite 源码实现
触发条件检查 refreshAfterWrite 时间,超时则触发异步刷新检查 expireAfterWrite 时间,超时则直接阻塞加载
锁控制加锁后异步加载,其他线程返回旧值加锁后同步加载,其他线程阻塞等待
线程模型独立线程池执行刷新任务无独立线程池,依赖请求线程加载

六、总结

Guava Cache 的 refreshAfterWrite 机制通过以下设计实现高性能:

  1. 惰性刷新:仅在访问时触发刷新判断,避免后台轮询。
  2. 异步加载:通过 ListenableFuture 和独立线程池解耦 I/O 操作。
  3. 细粒度锁Segment 级锁减少锁竞争,提升并发能力。
  4. 旧值返回:刷新期间其他线程仍可读取旧值,避免阻塞。

适用场景:读多写少、允许短暂不一致(如配置信息、商品详情页)。

相关文章:

  • CExercise_06_1递归_1汉诺塔_2对于n个盘子的汉诺塔问题,给定一个整数m,要求在控制台打印出m + 1步的移动轨迹。
  • 正则表达式和excel文件保存(python)
  • 《轨道力学讲义》——第七讲:交会对接技术
  • ros通信机制学习——latched持久化机制
  • 【深度学习基础】——机器的神经元:感知机
  • 提示工程指南学习记录(二)
  • 东方博宜OJ ——1335 - 土地分割
  • IDEA的常用设置(更新中......)
  • 云原生(Cloud Native)的详解、开发流程及同类软件对比
  • [ComfyUI] 最新控制模型EasyControl,吉卜力风格一键转绘
  • 08【基础学习】串口通信(三):收发数据包+数据校验
  • 某公司网络OSPF单区域配置
  • 作业帮前端面试题及参考答案 (100道面试题-上)
  • 交易所开发全流程解析:KYC与U盾在安全合规中的战略价值
  • 基于Ubuntu系统搭建51单片机开发环境的详细教程
  • Auto-Encoder --李宏毅机器学习笔记
  • 视觉算法+雾炮联动:开创智能降尘新时代
  • 基于unsloth微调大模型并上传到huggingface
  • zephyr RTOS 中 bt_le_adv_start函数的功能应用
  • 基础知识:离线安装docker、docker compose
  • 深化应用型人才培养,这所高校聘任行业企业专家深度参与专业设置
  • 国际市场开心果价格上涨35%,背后助力是一条点击过亿的短视频
  • 美肯塔基州长警告:关税或致美家庭年增数千美元支出
  • 学习时报头版评论:历史的车轮不会倒退
  • 雅生活服务:向雅居乐收购两家环保公司,总价约6060万元
  • 2025年中国航天工程有哪些重点任务?国家航天局介绍