Guava LocalCache源码分析:LocalCache的get、put、expand、refresh、remove、clear、cleanUp
Guava LocalCache源码分析:LocalCache的get、put、expand
- 前言
- 一、get
- 二、put
- 三、expand
前言
上篇文章,详细描写了Guava LocalCache怎样如ConcurrentHashMap对缓存数据进行了分段存储。本章主要针对LocalCache重要的几个接口进行说明。
一、get
@CanIgnoreReturnValue@Override@CheckForNullpublic V get(@CheckForNull Object key) {if (key == null) {return null;}int hash = hash(key);return segmentFor(hash).get(key, hash);}@CanIgnoreReturnValueV get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {int hash = hash(checkNotNull(key));return segmentFor(hash).get(key, hash, loader);}
如上代码,LocalCache的get方法首先根据key计算出hash值,并根据hash值找到对应的segment,再调用segment的get方法获取最终结果。
以传入loader参数的get方法为例,看一下segment如何获取值的。
@CanIgnoreReturnValueV get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {checkNotNull(key);checkNotNull(loader);try {if (count != 0) {//不要调用getLiveEntry,这将忽略正在加载的值//根据key和hash获取值ReferenceEntry<K, V> e = getEntry(key, hash);if (e != null) {//获取当前时间long now = map.ticker.read();//判断该值是否过期V value = getLiveValue(e, now);//如果未过期if (value != null) {//记录读取时间recordRead(e, now);//累计命中+1,这里Guava似乎认为当多条线程在更新统计数据时,//而不是细粒度同步控制的情况下,LongAdder比AtomicLong更好用。statsCounter.recordHits(1);//检查是否需要刷新,如果设置了刷新时长且过了刷新时长,则刷新,否则返回该值return scheduleRefresh(e, key, hash, value, now, loader);}//值已经过期ValueReference<K, V> valueReference = e.getValueReference();//如果增在加载if (valueReference.isLoading()) {//等待并返回加载后的值return waitForLoadingValue(e, key, valueReference);}}}//segment中为空或者未获取到值//加锁,尝试从加载中的值中获取,若获取不到则调用load方法。return lockedGetOrLoad(key, hash, loader);} catch (ExecutionException ee) {Throwable cause = ee.getCause();if (cause instanceof Error) {throw new ExecutionError((Error) cause);} else if (cause instanceof RuntimeException) {throw new UncheckedExecutionException(cause);}throw ee;} finally {//累计到一定读取次数后,清理超时缓存postReadCleanup();}}
相关调用逻辑如图所示:

二、put
public V put(K key, V value) {checkNotNull(key);checkNotNull(value);int hash = hash(key);return segmentFor(hash).put(key, hash, value, false);}
同样,LocalCache的put方法也是首先根据key计算出hash值,并根据hash值找到对应的segment,再调用segment的put方法。
V put(K key, int hash, V value, boolean onlyIfAbsent) {//直接加锁lock();try {long now = map.ticker.read();//清理过期缓存preWriteCleanup(now);//数量+1int newCount = this.count + 1;if (newCount > this.threshold) {//扩容expand();//因为扩容后的segment内的缓存数量可能会变化,所以重新计算newCount = this.count + 1;}//找到要插入的位置,并获取该位置的头节点AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;int index = hash & (table.length() - 1);ReferenceEntry<K, V> first = table.get(index);//寻找该key是否存在for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {K entryKey = e.getKey();if (e.getHash() == hash&& entryKey != null//判断key是否相等&& map.keyEquivalence.equivalent(key, entryKey)) {//意味着map中存在该key//获取对应的值ValueReference<K, V> valueReference = e.getValueReference();V entryValue = valueReference.get();//如果值是空的if (entryValue == null) {++modCount;//判断该值是否在等待删除中if (valueReference.isActive()) {//将旧值移放入移除通知队列中,主要是Guava Cache有移除回调机制,故不能直接移除,队列方便用于回调通知。enqueueNotification(key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED);//加入缓存setValue(e, key, value, now);//因为一删一增,所以,数量不变newCount = this.count; // count remains unchanged} else {//加入缓存setValue(e, key, value, now);//这里不知道为啥又算了一遍,可能是再setValue中存在某些机制导致count发生了变化?newCount = this.count + 1;}this.count = newCount; // write-volatile//移除旧值evictEntries(e);return null;} else if (onlyIfAbsent) {//onlyIfAbsent为true,如果存在于map中,仅更新访问时间recordLockedRead(e, now);return entryValue;} else {//删除现有缓存,计数保持不变++modCount;enqueueNotification(key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED);setValue(e, key, value, now);evictEntries(e);return entryValue;}}}//map中不存在,则插入++modCount;ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);setValue(newEntry, key, value, now);table.set(index, newEntry);newCount = this.count + 1;this.count = newCount; // write-volatileevictEntries(newEntry);return null;} finally {//解锁unlock();//清除过期缓存postWriteCleanup();}}
可见,整个put过程是用了锁保证执行的线程安全。
三、expand
LocalCache的扩容也是对段进行的扩容,当段的大小超过阈值时,便会出发扩容(详细见上面的put函数),段的阈值为段大小的3/4,而每次扩容段的大小会变为原来的2倍。
代码如下:
@GuardedBy("this")void expand() {AtomicReferenceArray<ReferenceEntry<K, V>> oldTable = table;int oldCapacity = oldTable.length();//如果容量超过最大容量,直接返回if (oldCapacity >= MAXIMUM_CAPACITY) {return;}int newCount = count;//新段的大小是旧段的两倍AtomicReferenceArray<ReferenceEntry<K, V>> newTable = newEntryArray(oldCapacity << 1);//阈值为新段大小的3/4threshold = newTable.length() * 3 / 4;//因为段扩容,所以要重新计算哈希映射int newMask = newTable.length() - 1;for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) {//我们需要保证对旧map的任何现有读取都可以继续进行。所以我们还不能清空旧段。ReferenceEntry<K, V> head = oldTable.get(oldIndex);if (head != null) {ReferenceEntry<K, V> next = head.getNext();int headIndex = head.getHash() & newMask;// Single node on listif (next == null) {//对于单个的节点的,直接插入新段中newTable.set(headIndex, head);} else {//重复使用列表末尾具有相同目标索引的连续节点序列。tail指向可重用序列中的第一个节点。ReferenceEntry<K, V> tail = head;int tailIndex = headIndex;for (ReferenceEntry<K, V> e = next; e != null; e = e.getNext()) {int newIndex = e.getHash() & newMask;if (newIndex != tailIndex) {tailIndex = newIndex;tail = e;}}//将可重用序列中的第一个节点放入新映射位置newTable.set(tailIndex, tail);//将头尾之间的节点重新映射到新段中for (ReferenceEntry<K, V> e = head; e != tail; e = e.getNext()) {int newIndex = e.getHash() & newMask;ReferenceEntry<K, V> newNext = newTable.get(newIndex);//拷贝当前节点作为新的头节点,并将映射位置的头节点链接到当前节点e的next。ReferenceEntry<K, V> newFirst = copyEntry(e, newNext);if (newFirst != null) {//如果拷贝的新头节点不为null,则set到新段中newTable.set(newIndex, newFirst);} else {//否则,删除eremoveCollectedEntry(e);newCount--;}}}}}table = newTable;this.count = newCount;}
其中,对段节点的重映射逻辑如图所示:

这里需要留意的是Cache在重映射时,是将后续节点作为头节点插入到冲突位中,即首插入。故,新表映射的链路顺序与旧表会有比较大的区别。
相关文章:
Guava LocalCache源码分析:LocalCache的get、put、expand、refresh、remove、clear、cleanUp
Guava LocalCache源码分析:LocalCache的get、put、expand 前言一、get二、put三、expand 前言 上篇文章,详细描写了Guava LocalCache怎样如ConcurrentHashMap对缓存数据进行了分段存储。本章主要针对LocalCache重要的几个接口进行说明。 一、get CanIg…...
linux-arm ubuntu18.04 qmqtt5.12.6 编译部署
安装 qt 查看qt 版本 : qmake -v 下载对应版本 qmqtt 解压下载的mqtt文件 进入qmqtt xxx/src 目录 在qt 安装目录中创建QtMqtt文件夹, - x86平台qt 默认目录为 /usr/include/x86_64-linux-gnu/qt5 - arm平台qt 默认目录为/us…...
阿里ChatSDK使用,开箱即用聊天框
介绍: 效果:智能助理 ChatSDK,是在ChatUI的基础上,结合阿里云智能客服的最佳实践,沉淀和总结出来的一个开箱即用的,可快速搭建智能对话机器人的框架。它简单易上手,通过简单的配置就能搭建出对…...
LangChain —— Message —— How to trim messages
文章目录 一、概述二、获取最后的 max_tokens 令牌三、获取第一个 max_tokens 令牌四、编写自定义令牌计数器五、连成链六、使用 ChatMessageHistory 一、概述 所有模型都有 有限的 上下文窗口,这意味着它们可以作为输入的 token 数量是有限的。如果你有很长的消息&…...
专升本-1.0.3(英语)-升本209天-星期二
自己要耐得住寂寞,守得住自己的初心,守得住自己的未来,然后不断地真实地面对自己,使自己不断地获得一个真实地成长,说真话办真事,自己总会有一条路了,说真话,办真事的那条路才是最为…...
集合媒体管理、分类、搜索于一体的开源利器:Stash
Stash:强大的媒体管理工具,让您的影音生活井井有条- 精选真开源,释放新价值。 概览 Stash是一个专为个人媒体管理而设计的开源工具,基于 Go 编写,支持自部署。它以用户友好的界面和强大的功能,满足了现代用…...
数仓工具—Hive语法之事务表更新Transactional Table Update
Hive事务表更新 众所周知,Apache Hive 是建立在 Hadoop HDFS 之上的数据仓库框架。由于它包含表,您可能希望根据数据的变化更新表记录。直到最近,Apache Hive 还不支持事务。从 Hive 0.14 及以上版本开始支持事务性表。您需要启用 ACID 属性才能在 Hive 查询中使用更新、删…...
系统架构师(每日一练2)
每日一练 1.为实现对象重用,COM支持两种形式的对象组装,在()重用形式下,一个外部对象拥有指向一个内部对象的唯一引用,外部对象只是把请求转发给内部对象;在()重用形式下,直接把内部对象的接口引用传给外部对象的客户…...
Django REST Framework(十)视图集-ViewSet
视图集(ViewSet)是 Django REST framework 中的一个高级特性,它允许你使用更少的代码来实现标准的 CRUD(创建、读取、更新、删除)操作。ViewSet 类本质上是基于 GenericAPIView 的,但它们提供了更多的默认行…...
sping总览
一、spring体系 1. spring是什么? 轻量级的开源的J2EE框架。它是一个容器框架,主要实现了ioc,同时又通过aop实现了面向切面编程,它又是一个中间层框架(万能胶)可以起一个连接作用,比如说把myba…...
【Godot4.2】MLTag类:HTML、XML通用标签类
概述 HTML和XML采用类似的标签形式。 之前在Godot中以函数库形式实现了网页标签和内容生成。能用,但是缺点也很明显。函数之间没有从属关系,但是多有依赖,而且没有划分出各种对象和类型。 如果以完全的面向对象形式来设计标签类或者元素类…...
美式键盘 QWERTY 布局的起源
注:机翻,未校对。 The QWERTY Keyboard Is Tech’s Biggest Unsolved Mystery QWERTY 键盘是科技界最大的未解之谜 It’s on your computer keyboard and your smartphone screen: QWERTY, the first six letters of the top row of the standard keybo…...
【JavaEE】HTTP(2)
🤡🤡🤡个人主页🤡🤡🤡 🤡🤡🤡JavaEE专栏🤡🤡🤡 🤡🤡🤡下一篇文章:【JavaEE】HTTP协议(…...
LinuxShell编程2——shell搭建Discuzz论坛网站
目录 一、环境准备 ①准备一台虚拟机 ②初始化虚拟机 1、关闭防火墙 2、关闭selinux 3、配置yum源 4、修改主机名 二、搭建LAMP环境 ①安装httpd(阿帕奇apache)服务器 查看是否安装过httpd 启动httpd 设置开机启动 查看状态 安装网络工具 测试 ②安装…...
.NET MAUI开源架构_1.学习资源分享
最近需要开发Android的App,想预研下使用.NET开源架构.NET MAUI来开发App程序。因此网上搜索了下相关资料,现在把我查询的结果记录下,方便后面学习。 1.官方文档 1.1MAUI官方学习网站 .NET Multi-Platform App UI 文档 - .NET MAUI | Micro…...
Unsloth 微调 Llama 3
本文参考: https://colab.research.google.com/drive/135ced7oHytdxu3N2DNe1Z0kqjyYIkDXp 改编自:https://blog.csdn.net/qq_38628046/article/details/138906504 文章目录 一、项目说明安装相关依赖下载模型和数据 二、训练1、加载 model、tokenizer2、…...
热修复的原理
热修复的原理 水一篇哈,完事儿后删掉热修复的原理 水一篇哈,完事儿后删掉 热修复的原理 Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,而他们加载类的时候都需要ClassLoader,…...
【对顶堆 优先队列】2102. 序列顺序查询
本文涉及知识点 对顶堆 优先队列 LeetCode 2102. 序列顺序查询 一个观光景点由它的名字 name 和景点评分 score 组成,其中 name 是所有观光景点中 唯一 的字符串,score 是一个整数。景点按照最好到最坏排序。景点评分 越高 ,这个景点越好。…...
Go 语言中的互斥锁 Mutex
Mutex 是一种互斥锁,名称来自 mutual exclusion,是一种用于控制多线程对共享资源的竞争访问的同步机制。在有的编程语言中,也将其称为锁(lock)。当一个线程获取互斥锁时,它将阻止其他线程对该资源的访问,直到该线程释放锁。这可以防止多个线程对共享资源进行冲突访问,从而…...
CSS 中的 ::before 和 ::after 伪元素
目录 一、CSS 伪元素 二、::before ::after 介绍 1、::before 2、::after 3、content 常用属性值 三、::before ::after 应用场景 1、设置统一字符 2、通过背景添加图片 3、添加装饰线 4、右侧展开箭头 5、对话框小三角 6、插入icon图标 一、CSS 伪元素 CSS伪元…...
2026年一键生成论文工具实测排行,哪款真正适合顺利通关?
2026 年学术 AI 论文工具已形成全流程、理工 / 社科、英文 / 中文、免费 / 付费的清晰分化。综合实测排行与场景适配,千笔AI 是中文全能首选,DeepSeek 学术版是理工开源首选,毕业之家是国内毕业专属首选。 一、2026 年实测排行 TOP5ÿ…...
【巴洛克AI生成合规白皮书】:基于梵蒂冈档案馆高清藏品训练的192个版权安全Prompt模板
更多请点击: https://codechina.net 第一章:巴洛克AI生成合规白皮书导论 巴洛克AI生成合规白皮书旨在为组织在部署和运营生成式人工智能系统时,提供一套可落地、可审计、可演进的合规治理框架。该白皮书聚焦于中国《生成式人工智能服务管理暂…...
14404黄大年茶思屋榜文144期第四题AI辅助故障自动检测、复现和故障自动定界定位
开源鸿蒙难题揭榜第四题:AI辅助故障自动检测复现定位 AI零偏差标准化脱敏解题全集 摘要 本文严格遵循AI无偏差标准化解题框架,完成鸿蒙第四期系统故障智能运维难题全维度规范化拆解,全文一字未改复刻官方脱敏原题内容,精准还原隐藏…...
AI设计泳装,能颠覆今夏潮流?
AI设计泳装,能颠覆今夏潮流? 夏日临近,泳装市场硝烟再起。然而,海量款式与消费者挑剔审美的矛盾日益尖锐——设计周期长、打版成本高、爆款命中率低,让无数商家深陷库存泥潭。如何破局?北京先智先行科技有限…...
为内部知识库问答系统集成 Taotoken 多模型增强回答多样性
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 为内部知识库问答系统集成 Taotoken 多模型增强回答多样性 在企业内部知识库中构建智能问答系统,核心目标之一是提供准…...
HTTP协议认识
什么是 Http 协议? 超文本传输协议,规定了浏览器与服务器通信的规则 Http 协议的特点? 面向连接、安全的协议(基于 TCP)基于请求响应模型的无状态的协议 按F12 一、状态码大类 状态码分类说明1xx响应中…...
你的Nmap脚本库该更新了!手把手教你管理、调试与编写自定义NSE脚本
从使用者到创造者:Nmap脚本引擎(NSE)深度管理指南 在渗透测试和安全评估领域,Nmap早已超越了简单的端口扫描工具定位,其强大的脚本引擎(NSE)使其成为网络安全专业人员的瑞士军刀。但大多数用户仅停留在基础脚本调用层面,未能充分释…...
大学生选择网络工程,后期就业方向有哪些?
每年高考填志愿那阵子,总有学弟学妹跑来问:"网络工程这个专业怎么样?毕业了好找工作吗?"说实话,这个问题不太好回答。不是方向少,而是方向太多,而且每个方向的天花板和薪资差距不小。 我当年也是稀里糊涂选的网络工程,入学才知道跟计算机科学不是一回事。但…...
ElevenLabs印地文语音质量崩塌真相(印地语TTS失效深度溯源):7类发音错误+5个未公开参数修复方案
更多请点击: https://intelliparadigm.com 第一章:ElevenLabs印地文语音质量崩塌的全局现象与影响评估 近期,ElevenLabs平台在印地语(Hindi)TTS合成任务中出现系统性语音质量退化,表现为音素错读、韵律断裂…...
手把手教你配置STC15F2K60S2的PCA引脚映射,灵活切换P1/P3/P2口输出PWM信号
STC15F2K60S2单片机PCA模块实战:三端口PWM信号自由切换指南 当你在蓝桥杯CT107D开发板上调试电机控制时,是否遇到过P1口被数码管占用却需要输出PWM的困境?STC15F2K60S2的PCA模块引脚重映射功能正是解决这类硬件冲突的利器。本文将带你深入掌…...
