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 的官方文档和源码。

相关推荐
天上掉下来个程小白14 分钟前
maven高级-01.分模块设计与开发
java·spring boot·后端·maven·maven高级
Foyo Designer20 分钟前
【 <一> 炼丹初探:JavaWeb 的起源与基础】之 Servlet 与 JSP 的协作:MVC 模式的雏形
java·开发语言·经验分享·程序人生·servlet·mvc·改行学it
傻啦嘿哟24 分钟前
Java开发者如何接入并使用DeepSeek
java·开发语言
n北斗43 分钟前
docker拉取失败
java·开发语言
WeiLai11121 小时前
面试基础--MySQL SQL 优化深度解析
java·后端·sql·mysql·面试·架构
舌尖上的五香1 小时前
FastExcel/EasyExcel简介以及源码解析
java
跟着汪老师学编程1 小时前
12、JavaEE概述
java·java-ee
佩服许仙1 小时前
List删除异常原理
java
神秘的t1 小时前
优选算法合集————双指针(专题三)
java·数据结构·算法·二分查找
liubing51881 小时前
解决java-jar报错:xxx.jar 中没有主清单属性的方法
java·pycharm·jar