最新要闻
- 持球还得练练!杰伦抢七战出现8失误 NBA历史第二多 即时
- 预算不够免签来凑!米兰开启免签大法,夏窗欲网罗多名自由球员
- 史莱克七怪神装公布,小舞二次发育,宁荣荣最美,小奥烟熏妆差评-当前热门
- 环球消息!演唱会门票秒罄!黄牛党捞钱术升级
- 速看:日本专家三年研究:中学生入学得到手机脑力直接停滞在小学
- 天天消息!网友评新一代LCD神机Redmi Note 12T Pro:卢伟冰焊门很准
- 省“春蕾计划+我的书房”项目在盐城东台启动 热文
- 环球资讯:从华为离职创业的天才少年刚拿了百度投资:估值被曝已超独角兽
- 曾遭霸凌的解说YammerS离世 DOTA2官微回应:深感痛心_全球观天下
- 每日热议!兄妹四人同时查出肠道肿瘤:1/4肠癌患者受家族因素影响
- 天玑9300要绝杀:采用4+4全大核架构 性能狙击A17功耗大降
- 京西产业引导基金签约发布 规模10.1亿元|当前热门
- 注意防暑!多地高温已超40℃
- 诚意满满 微星发布7大类PC新品:美与性能兼备 世界速讯
- 续航128公里 雅迪联手保时捷推出电动摩托车:卖4.5万
- 世界动态:丰田带头抵触汽车全面电动化 多家日本公司站队支持:不环保、自欺欺人
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
如何用ReadWriteLock实现一个通用的缓存中心?|环球今热点
摘要:在并发场景中,Java SDK中提供了ReadWriteLock来满足读多写少的场景。
本文分享自华为云社区《【高并发】基于ReadWriteLock开了个一款高性能缓存》,作者:冰 河。
写在前面
在实际工作中,有一种非常普遍的并发场景:那就是读多写少的场景。在这种场景下,为了优化程序的性能,我们经常使用缓存来提高应用的访问性能。因为缓存非常适合使用在读多写少的场景中。而在并发场景中,Java SDK中提供了ReadWriteLock来满足读多写少的场景。本文我们就来说说使用ReadWriteLock如何实现一个通用的缓存中心。
本文涉及的知识点有:
【资料图】
读写锁
说起读写锁,相信小伙伴们并不陌生。总体来说,读写锁需要遵循以下原则:
- 一个共享变量允许同时被多个读线程读取到。
- 一个共享变量在同一时刻只能被一个写线程进行写操作。
- 一个共享变量在被写线程执行写操作时,此时这个共享变量不能被读线程执行读操作。
这里,需要小伙伴们注意的是:读写锁和互斥锁的一个重要的区别就是:读写锁允许多个线程同时读共享变量,而互斥锁不允许。所以,在高并发场景下,读写锁的性能要高于互斥锁。但是,读写锁的写操作是互斥的,也就是说,使用读写锁时,一个共享变量在被写线程执行写操作时,此时这个共享变量不能被读线程执行读操作。
读写锁支持公平模式和非公平模式,具体是在ReentrantReadWriteLock的构造方法中传递一个boolean类型的变量来控制。
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this);}
另外,需要注意的一点是:在读写锁中,读锁调用newCondition()会抛出UnsupportedOperationException异常,也就是说:读锁不支持条件变量。
缓存实现
这里,我们使用ReadWriteLock快速实现一个缓存的通用工具类,总体代码如下所示。
public class ReadWriteLockCache{ private final Map m = new HashMap<>(); private final ReadWriteLock rwl = new ReentrantReadWriteLock(); // 读锁 private final Lock r = rwl.readLock(); // 写锁 private final Lock w = rwl.writeLock(); // 读缓存 public V get(K key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } // 写缓存 public V put(K key, V value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } }}
可以看到,在ReadWriteLockCache中,我们定义了两个泛型类型,K代表缓存的Key,V代表缓存的value。在ReadWriteLockCache类的内部,我们使用Map来缓存相应的数据,小伙伴都都知道HashMap并不是线程安全的类,所以,这里使用了读写锁来保证线程的安全性,例如,我们在get()方法中使用了读锁,get()方法可以被多个线程同时执行读操作;put()方法内部使用写锁,也就是说,put()方法在同一时刻只能有一个线程对缓存进行写操作。
这里需要注意的是:无论是读锁还是写锁,锁的释放操作都需要放到finally{}代码块中。
在以往的经验中,有两种向缓存中加载数据的方式,一种是:项目启动时,将数据全量加载到缓存中,一种是在项目运行期间,按需加载所需要的缓存数据。
接下来,我们就分别来看看全量加载缓存和按需加载缓存的方式。
全量加载缓存
全量加载缓存相对来说比较简单,就是在项目启动的时候,将数据一次性加载到缓存中,这种情况适用于缓存数据量不大,数据变动不频繁的场景,例如:可以缓存一些系统中的数据字典等信息。整个缓存加载的大体流程如下所示。
将数据全量加载到缓存后,后续就可以直接从缓存中读取相应的数据了。
全量加载缓存的代码实现比较简单,这里,我就直接使用如下代码进行演示。
public class ReadWriteLockCache{ private final Map m = new HashMap<>(); private final ReadWriteLock rwl = new ReentrantReadWriteLock(); // 读锁 private final Lock r = rwl.readLock(); // 写锁 private final Lock w = rwl.writeLock(); public ReadWriteLockCache(){ //查询数据库 List > list = .....; if(!CollectionUtils.isEmpty(list)){ list.parallelStream().forEach((f) ->{m.put(f.getK(), f.getV);}); } } // 读缓存 public V get(K key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } // 写缓存 public V put(K key, V value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } }}
按需加载缓存
按需加载缓存也可以叫作懒加载,就是说:需要加载的时候才会将数据加载到缓存。具体来说:就是程序启动的时候,不会将数据加载到缓存,当运行时,需要查询某些数据,首先检测缓存中是否存在需要的数据,如果存在,则直接读取缓存中的数据,如果不存在,则到数据库中查询数据,并将数据写入缓存。后续的读取操作,因为缓存中已经存在了相应的数据,直接返回缓存的数据即可。
这种查询缓存的方式适用于大多数缓存数据的场景。
我们可以使用如下代码来表示按需查询缓存的业务。
class ReadWriteLockCache{ private final Map m = new HashMap<>(); private final ReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); V get(K key) { V v = null; //读缓存 r.lock(); try { v = m.get(key); } finally{ r.unlock(); } //缓存中存在,返回 if(v != null) { return v; } //缓存中不存在,查询数据库 w.lock(); try { //再次验证缓存中是否存在数据 v = m.get(key); if(v == null){ //查询数据库 v=从数据库中查询出来的数据 m.put(key, v); } } finally{ w.unlock(); } return v; }}
这里,在get()方法中,首先从缓存中读取数据,此时,我们对查询缓存的操作添加了读锁,查询返回后,进行解锁操作。判断缓存中返回的数据是否为空,不为空,则直接返回数据;如果为空,则获取写锁,之后再次从缓存中读取数据,如果缓存中不存在数据,则查询数据库,将结果数据写入缓存,释放写锁。最终返回结果数据。
这里,有小伙伴可能会问:为啥程序都已经添加写锁了,在写锁内部为啥还要查询一次缓存呢?
这是因为在高并发的场景下,可能会存在多个线程来竞争写锁的现象。例如:第一次执行get()方法时,缓存中的数据为空。如果此时有三个线程同时调用get()方法,同时运行到 w.lock()代码处,由于写锁的排他性。此时只有一个线程会获取到写锁,其他两个线程则阻塞在w.lock()处。获取到写锁的线程继续往下执行查询数据库,将数据写入缓存,之后释放写锁。
此时,另外两个线程竞争写锁,某个线程会获取到锁,继续往下执行,如果在w.lock()后没有v = m.get(key); 再次查询缓存的数据,则这个线程会直接查询数据库,将数据写入缓存后释放写锁。最后一个线程同样会按照这个流程执行。
这里,实际上第一个线程已经查询过数据库,并且将数据写入缓存了,其他两个线程就没必要再次查询数据库了,直接从缓存中查询出相应的数据即可。所以,在w.lock()后添加v = m.get(key); 再次查询缓存的数据,能够有效的减少高并发场景下重复查询数据库的问题,提升系统的性能。
读写锁的升降级
关于锁的升降级,小伙伴们需要注意的是:在ReadWriteLock中,锁是不支持升级的,因为读锁还未释放时,此时获取写锁,就会导致写锁永久等待,相应的线程也会被阻塞而无法唤醒。
虽然不支持锁升级,但是ReadWriteLock支持锁降级,例如,我们来看看官方的ReentrantReadWriteLock示例,如下所示。
class CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); try { // Recheck state because another thread might have // acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } }}}
数据同步问题
首先,这里说的数据同步指的是数据源和数据缓存之间的数据同步,说的再直接一点,就是数据库和缓存之间的数据同步。
这里,我们可以采取三种方案来解决数据同步的问题,如下图所示
超时机制
这个比较好理解,就是在向缓存写入数据的时候,给一个超时时间,当缓存超时后,缓存的数据会自动从缓存中移除,此时程序再次访问缓存时,由于缓存中不存在相应的数据,查询数据库得到数据后,再将数据写入缓存。
定时更新缓存
这种方案是超时机制的增强版,在向缓存中写入数据的时候,同样给一个超时时间。与超时机制不同的是,在程序后台单独启动一个线程,定时查询数据库中的数据,然后将数据写入缓存中,这样能够在一定程度上避免缓存的穿透问题。
点击关注,第一时间了解华为云新鲜技术~
关键词:
如何用ReadWriteLock实现一个通用的缓存中心?|环球今热点
速读:13)流程控制语句
一文详解 Sa-Token 中的 SaSession 对象-世界百事通
世界观察:第十二单元 常用API
持球还得练练!杰伦抢七战出现8失误 NBA历史第二多 即时
预算不够免签来凑!米兰开启免签大法,夏窗欲网罗多名自由球员
史莱克七怪神装公布,小舞二次发育,宁荣荣最美,小奥烟熏妆差评-当前热门
环球消息!演唱会门票秒罄!黄牛党捞钱术升级
速看:日本专家三年研究:中学生入学得到手机脑力直接停滞在小学
天天消息!网友评新一代LCD神机Redmi Note 12T Pro:卢伟冰焊门很准
第十一单元 面向对象三:继承与多态_世界热资讯
NTP网络校时服务器 (GPS+北斗+恒温晶振)双系统设计与研究|看点
焦点简讯:[javascript/html] HTML中Location对象详解
省“春蕾计划+我的书房”项目在盐城东台启动 热文
环球资讯:从华为离职创业的天才少年刚拿了百度投资:估值被曝已超独角兽
曾遭霸凌的解说YammerS离世 DOTA2官微回应:深感痛心_全球观天下
每日热议!兄妹四人同时查出肠道肿瘤:1/4肠癌患者受家族因素影响
天玑9300要绝杀:采用4+4全大核架构 性能狙击A17功耗大降
京西产业引导基金签约发布 规模10.1亿元|当前热门
注意防暑!多地高温已超40℃
诚意满满 微星发布7大类PC新品:美与性能兼备 世界速讯
续航128公里 雅迪联手保时捷推出电动摩托车:卖4.5万
世界动态:丰田带头抵触汽车全面电动化 多家日本公司站队支持:不环保、自欺欺人
一脚踏入软绵云朵:361° 游鲸飞标洞洞鞋103元3.69折抄底
热门:易基因:全基因组ChIP-seq分析揭示细菌转录因子PhoB的基因内结合位点|mBio
直播源码平台搭建技术分享之直播短信功能
文档在线预览(三)使用js前端实现word、excel、pdf、ppt 在线预览|世界报资讯
天天看点:用一杯星巴克的钱,训练自己私有化的ChatGPT
mysql设置字段的排序规则对大小写敏感_焦点精选
51分09秒跑5公里! 96岁老太打破年龄组世界纪录
上海一老人误将地铁扶手当售卖机:拿起手机扫码
全球热头条丨AMD定制处理器瞩目:华硕ROG Ally掌机内部结构首次公布
鲁大师久用流畅度测试排名出炉:OPPO Find X6 Pro第一 天天短讯
环球新消息丨飞机起飞前男子以照顾师傅为由滞留商务舱:称不需要对号入座
神舟十六发射成功!03 你还有七个学生没毕业:网友点赞导师到太空跑数据去了
新洁能:5月29日融券净卖出4.04万股,连续3日累计净卖出4.99万股
Java实现打包压缩文件或文件夹生成zip以实现多文件批量下载
大语言模型技术原理-环球视讯
MySQL之运算符大全|每日速讯
第八单元 数组与集合
(Java)记一次通过API递归分页“爬取”网页数据的开发经历
神舟十六号发射圆满成功:大红屏再次点亮 环球新资讯
全脂/低脂可选:特仑苏纯牛奶2.7元/盒大促(商超6元)
中科院院士:电动车主可以往外卖电 一年能卖4000元-焦点简讯
世界短讯!“十项全能”是怎样一种体验?华为Mate X3绝对是大屏折叠旗舰天花板
世界视点!小米MIX Fold 3曝光:屏下摄像头+潜望长焦 再无短板
每日热讯!电力板块异动拉升 世茂能源两连板
企业内训一体化解决方案,布道师教学实训云平台【开源版上线】|今日热闻
世界快报:JS中的事件监听
最野性的福特SUV!探险者昆仑巅峰版上市:售39.98万
新资讯:媒体评“剩菜盲盒”被疯抢 越来越多年轻人喜爱:便宜、杜绝浪费食品
荣耀赵明:MagicOS 8.0会有巨大进步 某些层面跟鸿蒙流畅性不相上下-环球快资讯
天天实时:给老外来点中国车 比亚迪ATTO 3成海外多国销冠:跻进全球前10
每日简讯:PS5国内白菜价:但PS5游戏盘开始涨价了
MongoDB学习笔记:配置文件-世界今热点
天涯神贴合集(2023最新)
天天快资讯丨找数字专注力训练(找数字)
每日头条!刚被微软夸史上最可靠系统 Win11又惹祸:AMD显卡中招
天天讯息:知名电竞解说Yammers自杀 留遗书称曾遭霸凌:玩家晒视频怀念 再见了宝贝
国产电商二手剪辑卖货 拿捏欧美老外
女子用餐被收700元服务费却无服务 官方回应1对1管家服务:网友感慨
如何强制删除电脑文件内容(如何强制删除电脑文件)
收益率持续下行 同业存单市场走牛
世界快资讯丨【读财报】19家民营银行财报透视:网商银行、华瑞银行、新网银行不良率居前三
无敌性价比、能灭Zen4全家桶 Intel 14核酷睿i5-13600KF到手1969元
SSD大牌被卷到!铠侠RC20史低价:1TB 299元/2TB 599元
【世界新要闻】神舟十六号即将发射!三位航天员亮相出征仪式:要出差半年
天天热讯:今年最强LCD手机!Redmi Note 12T Pro游戏实测:《王者》《原神》几乎满帧
全球观点:是否具有派遣资格是什么意思_是否具有派遣资格
读数据压缩入门笔记02_二进制和熵
计划2030年前实现中国人登月 马斯克:中国航天比多数人想象得要强大
女子骨折做手术后左腿变长2.3厘米:鉴定十级伤残 医院拒赔
产油大国起内讧:俄罗斯低价供应大量石油 沙特都顶不住了|独家焦点
5月29日基金净值:信诚量化阿尔法股票A最新净值1.4838,跌0.3% 全球通讯
找狗(关于找狗的基本详情介绍) 当前最新
有这么个神人:被毒蛇咬173次 长期注射蛇毒 活了101岁
Flash死了!但小时候玩过的游戏 还不想死
地铁上 女子充电宝突然爆炸!现场烟雾弥漫
今晚油价或迎年内第四涨!加满一箱多花3.5元
3300秒!我国未来载人登月火箭主力发动机单台试车时长创新纪录 焦点滚动
【环球热闻】111完美邮箱_1163 com邮箱
【港股通】国泰航空(00293)据报接近订购波音777-8F货机-环球时快讯
土耳其总统埃尔多安再次赢得大选,将再度连任,外交部回应
5月29日基金净值:南方荣光A最新净值1.523,涨0.07% 当前观点
里程碑博士_里程碑的意思_今日看点
速看:武汉洪山融创智谷举办首届菁英联谊会,青年男女开启甜蜜之旅
黄果树天星洞景区小孩敲断钟乳石 工作人员称会尽快处理进行追责
国产动漫《斗罗大陆》终于迎来大结局 堪称“电影级”观影体验
德国一模特通过断骨增高将自己的身高从163增加至180厘米 手术风险极大
阿根廷国家队公布最新一期球队大名单 将由梅西与迪玛利亚领衔
东航全球首架交付国产大飞机C919顺利抵达 全球首次商业载客飞行成功
天天观点:淘宝账号登陆失败怎么回事_我的淘宝账户登录
2023.5.29Linux系统压缩打包|今日看点
环球看点!蚕豆迎丰收 “公司+专业合作社+农户”模式助农增收
蒙牛乳业宁夏工厂正式落成投产 为全球首座乳业全数智化工厂-天天快报
华硕发布49英寸QD-OLED带鱼屏显示器:5K分辨率 1000nits亮度
热点评!童年回忆满满 《王者荣耀》三款电玩新皮肤来了
全球今亮点!能玩破解游戏!任天堂回应下架《海豚模拟器》
全球快看点丨红蜘蛛新一代校色仪Spyder X2/X2 Ultra发布:最高支持2000nit亮度
今日看点:12)自定义函数