以下是 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
类的定义
Segment
是LocalCache
的内部类,继承自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. 锁的使用
- 在操作缓存时,先对目标段加锁,操作完成后释放锁。
- 使用
ReentrantLock
的lock()
和unlock()
方法实现加锁和解锁。
3. 并发性能
- 分段锁减少了锁竞争,提高了缓存的并发性能。
- 在高并发场景下,不同段的操作可以并行执行。
四、总结
LocalCache
的分段锁实现是其高并发性能的核心。通过将缓存数据划分为多个段,每个段独立加锁,减少了锁竞争并提高了并发性能。以下是分段锁的核心特点:
- 减少锁竞争:不同段的操作可以并行执行。
- 提高并发性能:在多线程环境下,分段锁能够显著提升缓存的吞吐量。
- 灵活性:段的数量可以根据并发级别动态调整。
如果需要更深入的源码分析或性能优化,可以参考 Guava 的官方文档和源码。