Guava LocalCache源码分析:LocalCache的get、put、expand、refresh、remove、clear、cleanUp

news2024/9/20 18:40:33

Guava LocalCache源码分析:LocalCache的get、put、expand

  • 前言
  • 一、get
  • 二、put
  • 三、expand

前言

上篇文章,详细描写了Guava LocalCache怎样如ConcurrentHashMap对缓存数据进行了分段存储。本章主要针对LocalCache重要的几个接口进行说明。

一、get

    @CanIgnoreReturnValue
    @Override
    @CheckForNull
    public V get(@CheckForNull Object key) {
        if (key == null) {
            return null;
        }
        int hash = hash(key);
        return segmentFor(hash).get(key, hash);
    }

    @CanIgnoreReturnValue
    V 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如何获取值的。

        @CanIgnoreReturnValue
        V 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);
                //数量+1
                int 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-volatile
                evictEntries(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/4
            threshold = 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 list
                    if (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 {
                                //否则,删除e
                                removeCollectedEntry(e);
                                newCount--;
                            }
                        }
                    }
                }
            }
            table = newTable;
            this.count = newCount;
        }

其中,对段节点的重映射逻辑如图所示:
在这里插入图片描述
这里需要留意的是Cache在重映射时,是将后续节点作为头节点插入到冲突位中,即首插入。故,新表映射的链路顺序与旧表会有比较大的区别。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1931518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[笔记]Fluke3563 振动分析仪

参考文档&#xff1a;Fluke 3563 Analysis Vibration Sensor system | Fluke 1.四大机械故障损伤原因 2.振动特征 福禄克做的示意图很棒&#xff1a; 不平衡对应转动轴的一倍频&#xff0c;不对中是2倍频&#xff0c;然后3~6倍频会有未紧固故障&#xff0c;更高频的位置是齿轮…

怎么压缩视频文件?简单的压缩视频方法分享

视频已成为我们日常生活中不可或缺的一部分。但随着视频质量的提高&#xff0c;文件大小也逐渐成为我们分享的阻碍。如何有效压缩视频文件&#xff0c;使其既能保持清晰&#xff0c;又能轻松分享&#xff1f;今天&#xff0c;给大家分享五种实用的视频压缩方法&#xff0c;快来…

ubuntu上模拟串口通信

前言 有时候写了一些串口相关的程序&#xff0c;需要调试的时候&#xff0c;又没有硬件&#xff0c;或者需要等其他模块完成才能一起联调。这样搭建环境费时费力&#xff0c;很多问题等到最后联调才发现就已经很晚了。 本文提供一种在ubuntu环境下模拟串口&#xff0c;直接就可…

性价比高的宠物空气净化器什么牌子好?热门养宠空气净化器分享

作为一名有6年经验的铲屎官&#xff0c;许多新手铲屎官可能听说过宠物空气净化器&#xff0c;但了解不多。实际上&#xff0c;宠物空气净化器是养猫家庭必备的小家电之一。它的大面积进风口能有效吸附空气中的浮毛和皮屑&#xff0c;专门的除臭技术可以去除猫咪带来的异味。宠物…

Python 视频水印批量添加器

功能如下可以 一、选择水印位置 二、批量添加水印 三、可添加文本或图片 # -*- 编码&#xff1a;utf-8 -*- import cv2 import os import numpy as np from moviepy.editor import VideoFileClip from concurrent.futures import ThreadPoolExecutor import tkinter as tk fro…

【深度学习】FaceChain-SuDe,免训练,AI换脸

https://arxiv.org/abs/2403.06775 FaceChain-SuDe: Building Derived Class to Inherit Category Attributes for One-shot Subject-Driven Generation 摘要 最近&#xff0c;基于主体驱动的生成技术由于其个性化文本到图像生成的能力&#xff0c;受到了广泛关注。典型的研…

PostgreSQL使用(二)

说明&#xff1a;本文介绍PostgreSQL的DML语言&#xff1b; 插入数据 -- 1.全字段插入&#xff0c;字段名可以省略 insert into tb_student values (1, 张三, 1990-01-01, 88.88);-- 2.部分字段插入&#xff0c;字段名必须写全 insert into tb_student (id, name) values (2,…

分享3个好用的启动盘u盘制作工具

对于经常需要安装维护电脑的同学&#xff0c;制作一个可启动的U盘是非常有必要的。小编今天就和大家分享三款优秀的U盘启动盘制作工具&#xff1a;Ventoy、UltraISO和Rufus。 1. Ventoy Ventoy是一款开源的启动U盘制作工具&#xff0c;它支持将ISO、WIM、IMG、VHD(x)和EFI等类…

SpringMVC 控制层框架-上

一、SpringMVC简介 1. 介绍 Spring Web MVC 是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring Framework 中。在控制层框架经历Srust、WebWork、Strust2等诸多产品的历代更迭之后&#xff0c;目前业界普遍选择了SpringMVC 作为Java EE项目表述层开发的首…

如何通过网络快速搜寻到自己的STM32设备

目录 一、问题概述 二、解决思路 三、代码实现 1.创建任务 2.UDP广播接收 一、问题概述 以前一直用RS232串口修改设备配置信息&#xff0c;但是现场施工人员的232线太细&#xff0c;经常容易断掉&#xff0c;这次准备用网口去修改&#xff0c;遇到了一个问题&#xff0c;…

WINUI或WPF灵活使用样式、控件模板、自定义控件、用户控件

在WINUI与WPF 中&#xff0c;控件模板&#xff08;ControlTemplate&#xff09;、样式&#xff08;Style&#xff09;、自定义控件&#xff08;CustomControl&#xff09;和用户控件&#xff08;UserControl&#xff09;都是构建复杂和灵活用户界面的重要工具&#xff0c;但它们…

vue3 中 lottie-web 封装组件

用到的JSON文件在“我的资源”里&#xff0c;下面这个链接直达 下面的代码中用到的JSON数据源 Lottie.vue <script setup> import { ref, onMounted } from vue import lottie from lottie-web// 设置组件参数 const props defineProps({renderer: {type: String,def…

手把手带你白嫖10年服务器

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 手把手带你白嫖10年服务器 如何获取如何使用成果个人网站 个人邮箱服务 重要的话重要说&#xff…

Ubuntu部署K8S集群-图文并茂(超详细)

Ubuntu部署K8S集群 1. 模版机系统环境准备1.1 安装Ubuntu1.2 设置静态IP地址 2. 主机准备2.1 使用模板机创建主机2.2 主机配置2.2.1 修改静态IP2.2.2 修改主机名2.2.3 主机名-IP地址解析2.2.4 时间同步2.2.5 内核转发、网桥过滤配置2.2.6 安装ipset和ipvsadm2.2.7 关闭SWAP分区…

【射频器件供应】Flann Microwave

国家 United Kingdom 地址 Flann Microwave Ltd Dunmere Road Bodmin, Cornwall PL31 2QL United Kingdom Flann Microwave于1956年成立于泰晤士河畔金斯顿萨里。在过去的四十年里&#xff0c;Flann Microwave一直是市场领先的天线设计公司&#xff0c;其精密微波器件和测试频…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【获取密钥属性(ArkTS)】

获取密钥属性(ArkTS) HUKS提供了接口供业务获取指定密钥的相关属性。在获取指定密钥属性前&#xff0c;需要确保已在HUKS中生成或导入持久化存储的密钥。 开发步骤 指定待查询的密钥别名keyAlias&#xff0c;密钥别名最大长度为64字节。调用接口[getKeyItemProperties]&…

Qt类 | QAbstractButton类详解

文章目录 一、QAbstractButton类介绍二、Properties&#xff08;属性&#xff09;三、Public Functions&#xff08;公共函数&#xff09;1.构造函数2.autoExclusive与setAutoExclusive函数--获取/设置自动互斥状态3.autoRepeat与setAutoRepeat函数--获取/设置自动重复状态4.au…

Magic Insert的奇特功能

当然可以&#xff01;让我为你详细介绍一下 Magic Insert 这个有趣的项目。 Magic Insert 是一个基于 AI 技术的创意工具&#xff0c;它允许我们从一张图像中提取一个主题&#xff0c;并将其以不同风格插入到另一张图像中&#xff0c;使得插入效果既符合目标图像的风格&#x…

Arduino呼吸灯

本次学习的内容 1、信号的输入与输出以及信号的分类。 2、理解数字信号与模拟信号以及它们的区别。 3、学会通过模拟输出的方式完成灯的呼吸效果。 Arduino中信号的分类 模拟信号|数字信号 模拟信号&#xff1a;是指用连续变化的物理量所表达的信息&#xff0c;如温度、湿…

基于springboot和mybatis的RealWorld后端项目实战一之hello-springboot

新建Maven项目 注意archetype选择quickstart pom.xml 修改App.java App.java同级目录新增controller包 HelloController.java package org.example.controller;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotatio…