LoadingCache源码剖析之缓存加载实现
其他文章:https://blog.csdn.net/define_us/article/details/111656019
随着互联网的发展,数据量不断增长,用户对性能要求的不断提升,在开发项目中使用缓存已经是必不可少的一种手段了。我们会将一些很少或者较少变化,对及时性要求不高的数据存放在缓存中,以减少数据库的压力和负载。常用的缓存分为堆内缓存,堆外缓存,以及分布式缓存。而谈到堆内缓存,比较常用的就是Guava里提供的LoadingCache。
本文中会从源码角度来分析LoadingCache中数据是如何被加载到缓存中,如何在多线程的场景下保证只有一个线程会去加载缓存数据。这一点在实际项目中是至关重要的,试想一下如果有100个线程同时到达,并且同时去数据库里读取数据并加载到缓存,很有可能就会发生缓存击穿,造成数据库负载急剧上升,更有甚者可能会直接搞挂数据库。
直接来一段Code Snippet
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES).removalListener(MY_LISTENER).build(new CacheLoader<Key, Graph>() {public Graph load(Key key) throws AnyException {return createExpensiveGraph(key);}});
上面这段代码是最常见的使用LoadingCache的方式
创建了一个缓存实例
指定最多可以存1000个缓存项
在加载缓存后10分钟过期
注册了一个自定义的CacheLoader来告诉LoadingCache如何在缓存失效后加载缓存。
但是这段代码有一个非常致命的问题就是当缓存不存在或者过期的情况下,LoadingCache只会允许一个请求去加载缓存,其他并发请求会阻塞在那边直至缓存加载完毕,那如果加载缓存过程比较慢的话,就会造成并发请求被阻塞,影响服务的整体性能,下面的这段代码模拟了这种情况。
模拟并发请求的代码
控制台输出
那LoadingCache究竟是如何实现上文所说的这种机制的呢?让我们一起顺着cache.get(“anyKey”)语句来探索下源码。
可以看到在这个方法里调用了localCache.getOrLoad(key)方法,localCache的类型是LocalCache,它继承了AbstractMap并且实现了ConcurrentMap接口。看到这里大家可能会问不是很多地方都说实现一个缓存只要使用LinkedHashMap就可以了吗?为什么还要重新去实现一遍呢?其实这里的关键在于LinkedHashMap并不是线程安全的,即使你在外部调用的地方去加上锁,那锁也是非常粗粒度的,然而LocalCache借鉴了ConcurrentHashMap的实现方式在内部使用了分段锁,大大提高了性能。这一块源码在这里就不展开了,如果大家有兴趣的话可以自己去探究下。
在继续往里跟,可以看到调用了一个重载的get方法,还传了一个defaultLoader,这个defaultLoader就是我们在初始化LoadingCache时定义的那个CacheLoader,告诉LoadingCache如何去加载缓存。
LocalCache.get
这个重载的get方法里首先先计算出缓存key的hash code,然后找到hash code所在的Segment,再从Segment里尝试获取缓存值。
继续来看segmentFor(hash).get(key,hash,loader)中的get方法
现在来看一下上文提到的waitForLoadingValue方法,这个方法会调用Future的阻塞get方法去获取缓存值,如果另外一个线程在加载缓存,当前线程会阻塞直到缓存加载完毕,如下图。
再看一下lockedGetOrLoad方法,这个方法中主要做的就是加锁,然后看缓存项是否存在,或者是否已经被其他线程加载,如果没有的话,就尝试用一开始用户传进来的loader加载缓存。
所以从LoadingCache的源码中可以看到,LoadingCache是会保证同一时间只有一个线程去加载缓存,从而防止缓存击穿的问题发生,但是如果缓存加载需要比较长的时间的话,会导致其他的线程都阻塞在那边,影响系统的可用性,在下篇文章中会介绍如何利用LoadingCache的refresh机制来解决这个问题.
LoadingCache源码剖析之缓存加载实现相关推荐
- android资源加载流程6,FrameWork源码解析(6)-AssetManager加载资源过程
之前一段时间项目比较忙所以一直没有更新,接下来准备把插件化系列的文章写完,今天我们就先跳过ContentProvider源码解析来讲资源加载相关的知识,资源加载可以说是插件化非常重要的一环,我们很有必 ...
- Android 11.0 Settings源码分析 - 主界面加载
Android 11.0 Settings源码分析 - 主界面加载 本篇主要记录AndroidR Settings源码主界面加载流程,方便后续工作调试其流程. Settings代码路径: packag ...
- 基于 GoogleMap 离线 API 源码在内网中加载卫星地图的方法
1. 概述 我们之前为大家分享过在三维地球开源平台离线加载卫星影像的方法,主要包括基于桌面端的OsgEarth开源三维地球和基于Web端的Cesium开源三维地球等平台的局域网离线加载. 另外,也为大 ...
- YOLOV5源码解读(数据集加载和增强)
YOLOV5源码解读系列文章目录 数据集加载和增强 loss计算 前言 此篇为yolov5 3.1 版本,官方地址[https://github.com/ultralytics/yolov5] 看源代 ...
- 从源码解析-结合Activity加载流程深入理解ActivityThrad的工作逻辑
ActivityThread源码解析 前言 类简称 类简介 一 二 三 四 五 代理和桩的理解 ActivityThread ActivityThread.main AT.attach AMN.get ...
- JVM源码阅读-本地库加载流程和原理
前言 本文主要研究OpenJDK中JVM源码中涉及到native本地库的加载流程和原理的部分.主要目的是为了了解本地库是如何被加载到虚拟机,以及是如何找到并执行本地库里的本地方法,以及JNI的 JNI ...
- Spring源码解析-applicationContext.xml加载和bean的注册
applicationContext文件加载和bean注册流程 Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎 ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- 有没有code能改xml内容_Spring源码解析-applicationContext.xml加载和bean的注册
applicationContext文件加载和bean注册流程 Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎 ...
最新文章
- 节后招人平均工资9000上热搜,为什么有些人去哪里都值钱?
- JavaScript修饰器-让代码更干净
- 京东前端:PhantomJS 和NodeJS在网站前端监控平台的最佳实践
- 决策树 bagging boosting 的区别
- android实现语音合成
- mysql查看导入大小_mysql 数据导入、导出,及库大小查看
- 途牛 “特产频道”上线 深化目的地服务网络
- git branch看不到分支_这份Git 日常操作清单,你都用到了吗
- 区块链应用如何实现资金盘分红
- 25个最好免费下载电子书(Ebooks)的网站
- Adobe Acrobat Pro 2017安装
- 湖南省工业技师学院计算机证,湖南省工业技师学院
- 在JavaScript中改变鼠标指针样式的方法
- python 频数统计_日常答疑:Python实现分类频数统计
- Ubuntu20.04中fastdfs,nginx的安装和配置(apt-get安装nginx添加fastdfs-nginx-module模块)
- jQuery插件jquery.fullPage.js
- android 模拟器实现发短信
- 进销存系统 通用进销存行业商用财务系统
- 【TWVRP】模拟退火算法求解带时间窗的车辆路径规划问题【含Matlab源码 160期】
- 基于STM32+DAC+DMA和AD9850的波形发生器