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

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();}}

相关调用逻辑如图所示:
LocalCache的get方法

二、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源码分析&#xff1a;LocalCache的get、put、expand 前言一、get二、put三、expand 前言 上篇文章&#xff0c;详细描写了Guava LocalCache怎样如ConcurrentHashMap对缓存数据进行了分段存储。本章主要针对LocalCache重要的几个接口进行说明。 一、get CanIg…...

linux-arm ubuntu18.04 qmqtt5.12.6 编译部署

安装 qt 查看qt 版本 &#xff1a; qmake -v 下载对应版本 qmqtt 解压下载的mqtt文件 进入qmqtt xxx/src 目录 在qt 安装目录中创建QtMqtt文件夹&#xff0c; &#xff0d; x86平台qt 默认目录为 /usr/include/x86_64-linux-gnu/qt5 &#xff0d; arm平台qt 默认目录为/us…...

阿里ChatSDK使用,开箱即用聊天框

介绍&#xff1a; 效果&#xff1a;智能助理 ChatSDK&#xff0c;是在ChatUI的基础上&#xff0c;结合阿里云智能客服的最佳实践&#xff0c;沉淀和总结出来的一个开箱即用的&#xff0c;可快速搭建智能对话机器人的框架。它简单易上手&#xff0c;通过简单的配置就能搭建出对…...

LangChain —— Message —— How to trim messages

文章目录 一、概述二、获取最后的 max_tokens 令牌三、获取第一个 max_tokens 令牌四、编写自定义令牌计数器五、连成链六、使用 ChatMessageHistory 一、概述 所有模型都有 有限的 上下文窗口&#xff0c;这意味着它们可以作为输入的 token 数量是有限的。如果你有很长的消息&…...

专升本-1.0.3(英语)-升本209天-星期二

自己要耐得住寂寞&#xff0c;守得住自己的初心&#xff0c;守得住自己的未来&#xff0c;然后不断地真实地面对自己&#xff0c;使自己不断地获得一个真实地成长&#xff0c;说真话办真事&#xff0c;自己总会有一条路了&#xff0c;说真话&#xff0c;办真事的那条路才是最为…...

集合媒体管理、分类、搜索于一体的开源利器:Stash

Stash&#xff1a;强大的媒体管理工具&#xff0c;让您的影音生活井井有条- 精选真开源&#xff0c;释放新价值。 概览 Stash是一个专为个人媒体管理而设计的开源工具&#xff0c;基于 Go 编写&#xff0c;支持自部署。它以用户友好的界面和强大的功能&#xff0c;满足了现代用…...

数仓工具—Hive语法之事务表更新Transactional Table Update

Hive事务表更新 众所周知,Apache Hive 是建立在 Hadoop HDFS 之上的数据仓库框架。由于它包含表,您可能希望根据数据的变化更新表记录。直到最近,Apache Hive 还不支持事务。从 Hive 0.14 及以上版本开始支持事务性表。您需要启用 ACID 属性才能在 Hive 查询中使用更新、删…...

系统架构师(每日一练2)

每日一练 1.为实现对象重用&#xff0c;COM支持两种形式的对象组装&#xff0c;在()重用形式下&#xff0c;一个外部对象拥有指向一个内部对象的唯一引用&#xff0c;外部对象只是把请求转发给内部对象;在()重用形式下&#xff0c;直接把内部对象的接口引用传给外部对象的客户…...

Django REST Framework(十)视图集-ViewSet

视图集&#xff08;ViewSet&#xff09;是 Django REST framework 中的一个高级特性&#xff0c;它允许你使用更少的代码来实现标准的 CRUD&#xff08;创建、读取、更新、删除&#xff09;操作。ViewSet 类本质上是基于 GenericAPIView 的&#xff0c;但它们提供了更多的默认行…...

sping总览

一、spring体系 1. spring是什么&#xff1f; 轻量级的开源的J2EE框架。它是一个容器框架&#xff0c;主要实现了ioc&#xff0c;同时又通过aop实现了面向切面编程&#xff0c;它又是一个中间层框架&#xff08;万能胶&#xff09;可以起一个连接作用&#xff0c;比如说把myba…...

【Godot4.2】MLTag类:HTML、XML通用标签类

概述 HTML和XML采用类似的标签形式。 之前在Godot中以函数库形式实现了网页标签和内容生成。能用&#xff0c;但是缺点也很明显。函数之间没有从属关系&#xff0c;但是多有依赖&#xff0c;而且没有划分出各种对象和类型。 如果以完全的面向对象形式来设计标签类或者元素类…...

美式键盘 QWERTY 布局的起源

注&#xff1a;机翻&#xff0c;未校对。 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)

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;下一篇文章&#xff1a;【JavaEE】HTTP协议(…...

LinuxShell编程2——shell搭建Discuzz论坛网站

目录 一、环境准备 ①准备一台虚拟机 ②初始化虚拟机 1、关闭防火墙 2、关闭selinux 3、配置yum源 4、修改主机名 二、搭建LAMP环境 ①安装httpd(阿帕奇apache&#xff09;服务器 查看是否安装过httpd 启动httpd 设置开机启动 查看状态 安装网络工具 测试 ②安装…...

.NET MAUI开源架构_1.学习资源分享

最近需要开发Android的App&#xff0c;想预研下使用.NET开源架构.NET MAUI来开发App程序。因此网上搜索了下相关资料&#xff0c;现在把我查询的结果记录下&#xff0c;方便后面学习。 1.官方文档 1.1MAUI官方学习网站 .NET Multi-Platform App UI 文档 - .NET MAUI | Micro…...

Unsloth 微调 Llama 3

本文参考&#xff1a; https://colab.research.google.com/drive/135ced7oHytdxu3N2DNe1Z0kqjyYIkDXp 改编自&#xff1a;https://blog.csdn.net/qq_38628046/article/details/138906504 文章目录 一、项目说明安装相关依赖下载模型和数据 二、训练1、加载 model、tokenizer2、…...

热修复的原理

热修复的原理 水一篇哈&#xff0c;完事儿后删掉热修复的原理 水一篇哈&#xff0c;完事儿后删掉 热修复的原理 Java虚拟机 —— JVM 是加载类的class文件的&#xff0c;而Android虚拟机——Dalvik/ART VM 是加载类的dex文件&#xff0c;而他们加载类的时候都需要ClassLoader,…...

【对顶堆 优先队列】2102. 序列顺序查询

本文涉及知识点 对顶堆 优先队列 LeetCode 2102. 序列顺序查询 一个观光景点由它的名字 name 和景点评分 score 组成&#xff0c;其中 name 是所有观光景点中 唯一 的字符串&#xff0c;score 是一个整数。景点按照最好到最坏排序。景点评分 越高 &#xff0c;这个景点越好。…...

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伪元…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...