Guava Cache 中LocalCache的分段锁实现

以下是 Guava Cache 中 LocalCache 分段锁实现的核心流程图 ,使用 Mermaid 语法绘制。该流程图展示了 Segment 的初始化缓存操作(获取和写入) 以及 并发控制机制
获取 写入 是 否 是 否 开始 初始化 Segment 计算段索引 操作类型 获取缓存条目 写入缓存条目 加锁 查找缓存条目 条目是否存在? 返回条目值 返回 null 解锁 加锁 条目是否存在? 更新条目值 插入新条目 解锁 结束


流程图说明

1. 初始化 Segment
  • LocalCache 初始化时,创建多个 Segment,每个 Segment 管理一部分缓存数据。
2. 计算段索引
  • 在操作缓存时,通过哈希函数计算键的段索引。
  • 公式:segmentIndex = hash(key) & (segments.length - 1)
3. 操作类型
  • 判断当前操作是 获取缓存条目 还是 写入缓存条目
4. 获取缓存条目
  • 加锁:对目标段加锁。
  • 查找缓存条目:在哈希表中查找条目。
  • 如果条目存在,则返回其值;否则返回 null
  • 解锁:释放锁。
5. 写入缓存条目
  • 加锁:对目标段加锁。
  • 判断条目是否存在:
    • 如果条目存在,则更新其值。
    • 如果条目不存在,则插入新条目。
  • 解锁:释放锁。
6. 结束
  • 操作完成,流程结束。

以下是核心代码实现逻辑

LocalCache 是 Guava Cache 的核心实现类,其 分段锁(Segment-based Locking) 机制是实现高并发访问的关键。以下是 LocalCache 分段锁实现的核心代码说明,包括 Segment 类的设计锁的实现并发控制机制


一、LocalCache 的分段锁设计

1. Segment 类的定义

  • SegmentLocalCache 的内部类,继承自 ReentrantLock
  • 每个 Segment 负责管理一部分缓存数据,并包含一个独立的锁。

2. Segment 的核心字段

  • table:哈希表,存储缓存条目(ReferenceEntry<K, V>)。
  • count:当前段中的条目数量。
  • modCount:修改次数,用于检测并发修改。
  • threshold:哈希表的扩容阈值。
  • maxSegmentWeight:当前段的最大权重(用于基于权重的淘汰策略)。

二、Segment 类核心代码说明

以下是 Segment 类的核心代码片段及其说明:

1. Segment 类的初始化

java 复制代码
final class Segment<K, V> extends ReentrantLock {
    // 哈希表,存储缓存条目
    volatile AtomicReferenceArray<ReferenceEntry<K, V>> table;
    // 当前段中的条目数量
    int count;
    // 修改次数,用于检测并发修改
    int modCount;
    // 哈希表的扩容阈值
    int threshold;
    // 当前段的最大权重
    long maxSegmentWeight;

    Segment(LocalCache<K, V> map, int initialCapacity, long maxSegmentWeight) {
        this.map = map;
        this.maxSegmentWeight = maxSegmentWeight;
        // 初始化哈希表
        initTable(newEntryArray(initialCapacity));
    }

    // 初始化哈希表
    AtomicReferenceArray<ReferenceEntry<K, V>> newEntryArray(int size) {
        return new AtomicReferenceArray<>(size);
    }

    void initTable(AtomicReferenceArray<ReferenceEntry<K, V>> newTable) {
        this.threshold = newTable.length() * 3 / 4; // 设置扩容阈值
        this.table = newTable;
    }
}

说明

  • table 是一个 AtomicReferenceArray,用于存储缓存条目。
  • initTable 方法初始化哈希表,并设置扩容阈值。

2. 获取缓存条目

java 复制代码
V get(Object key, int hash) {
    lock(); // 加锁
    try {
        // 查找缓存条目
        ReferenceEntry<K, V> entry = getEntry(key, hash);
        if (entry != null) {
            return entry.getValue();
        }
        return null;
    } finally {
        unlock(); // 解锁
    }
}

// 查找缓存条目
ReferenceEntry<K, V> getEntry(Object key, int hash) {
    AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
    int index = hash & (table.length() - 1); // 计算哈希索引
    ReferenceEntry<K, V> first = table.get(index);
    for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
        if (e.getHash() == hash && map.keyEquivalence.equivalent(key, e.getKey())) {
            return e;
        }
    }
    return null;
}

说明

  • 在获取缓存条目时,先对当前段加锁。
  • getEntry 方法通过哈希索引查找缓存条目。
  • 如果找到条目,则返回其值;否则返回 null
  • 最后释放锁。

3. 写入缓存条目

java 复制代码
V put(K key, int hash, V value) {
    lock(); // 加锁
    try {
        // 插入或更新缓存条目
        ReferenceEntry<K, V> entry = getEntry(key, hash);
        if (entry != null) {
            V oldValue = entry.getValue();
            entry.setValue(value);
            return oldValue;
        } else {
            insert(key, hash, value);
            return null;
        }
    } finally {
        unlock(); // 解锁
    }
}

// 插入缓存条目
void insert(K key, int hash, V value) {
    AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
    int index = hash & (table.length() - 1); // 计算哈希索引
    ReferenceEntry<K, V> first = table.get(index);
    ReferenceEntry<K, V> newEntry = map.entryFactory.newEntry(key, hash, first);
    table.set(index, newEntry);
    newEntry.setValue(value);
    count++;
    modCount++;
}

说明

  • 在写入缓存条目时,先对当前段加锁。
  • 如果条目已存在,则更新其值。
  • 如果条目不存在,则插入新条目。
  • 最后释放锁。

4. 扩容机制

java 复制代码
void expand() {
    AtomicReferenceArray<ReferenceEntry<K, V>> oldTable = this.table;
    int oldCapacity = oldTable.length();
    if (oldCapacity >= MAXIMUM_CAPACITY) {
        return;
    }
    int newCapacity = oldCapacity << 1; // 容量加倍
    AtomicReferenceArray<ReferenceEntry<K, V>> newTable = newEntryArray(newCapacity);
    threshold = newCapacity * 3 / 4; // 更新扩容阈值
    transfer(oldTable, newTable); // 迁移数据
    table = newTable;
}

// 迁移数据
void transfer(AtomicReferenceArray<ReferenceEntry<K, V>> oldTable, AtomicReferenceArray<ReferenceEntry<K, V>> newTable) {
    for (int i = 0; i < oldTable.length(); i++) {
        ReferenceEntry<K, V> entry = oldTable.get(i);
        if (entry != null) {
            int newIndex = entry.getHash() & (newTable.length() - 1); // 计算新索引
            newTable.set(newIndex, entry);
        }
    }
}

说明

  • 当哈希表的条目数量超过扩容阈值时,触发扩容。
  • 新哈希表的容量为旧哈希表的两倍。
  • 将旧哈希表中的数据迁移到新哈希表。

三、分段锁的并发控制

1. 锁的粒度

  • 每个 Segment 独立加锁,锁的粒度是段级别。
  • 不同段的操作可以并行执行,而同一段的操作需要串行执行。

2. 锁的使用

  • 在操作缓存时,先对目标段加锁,操作完成后释放锁。
  • 使用 ReentrantLocklock()unlock() 方法实现加锁和解锁。

3. 并发性能

  • 分段锁减少了锁竞争,提高了缓存的并发性能。
  • 在高并发场景下,不同段的操作可以并行执行。

四、总结

LocalCache 的分段锁实现是其高并发性能的核心。通过将缓存数据划分为多个段,每个段独立加锁,减少了锁竞争并提高了并发性能。以下是分段锁的核心特点:

  1. 减少锁竞争:不同段的操作可以并行执行。
  2. 提高并发性能:在多线程环境下,分段锁能够显著提升缓存的吞吐量。
  3. 灵活性:段的数量可以根据并发级别动态调整。

如果需要更深入的源码分析或性能优化,可以参考 Guava 的官方文档和源码。

相关推荐
源码_V_saaskw17 分钟前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨26 分钟前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
双力臂4041 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
心之语歌1 小时前
Spring AI MCP 客户端
人工智能·spring·github
Edingbrugh.南空1 小时前
Aerospike与Redis深度对比:从架构到性能的全方位解析
java·开发语言·spring
QQ_4376643142 小时前
C++11 右值引用 Lambda 表达式
java·开发语言·c++
永卿0012 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式
誰能久伴不乏2 小时前
Linux如何执行系统调用及高效执行系统调用:深入浅出的解析
java·服务器·前端
慕y2743 小时前
Java学习第七十二部分——Zookeeper
java·学习·java-zookeeper
midsummer_woo3 小时前
基于spring boot的医院挂号就诊系统(源码+论文)
java·spring boot·后端