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

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 类的初始化

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. 获取缓存条目

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. 写入缓存条目

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. 扩容机制

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


http://www.kler.cn/a/578942.html

相关文章:

  • UV,纹理,材质,对象
  • electron的通信方式(三种)
  • 健康养生:为生活注入活力
  • 深度链接技术解析:openinstall如何通过场景还原优化用户体验?
  • 【已解决】error setting certificate verify locations
  • 用套接字在网络中传送对象的时候为什么需要序列化?
  • Mac安装jdk教程
  • 深入探讨 Docker 层次结构及其备份策略20250309
  • 《基于Selenium的网页聊天室自动化测试实战报告》
  • android flow中collect和collectLatest的区别
  • [Kubernetes] 7控制平面组件
  • Django ORM 中的 RelatedManager 特殊方法
  • TypeScript系列06-模块系统与命名空间
  • neo4j随笔-将csv文件导入知识图谱
  • 【Java代码审计 | 第八篇】文件操作漏洞成因及防范
  • IntelliJ IDEA 2021版创建springboot项目的五种方式
  • javaEE初阶————多线程进阶(1)
  • 大整数加法(信息学奥赛一本通-1168)
  • NoSQL数据库系统Cassandra学习笔记
  • Prompt engineering设计原则