当前位置:首页 > 科技  > 软件

十分钟手撸一款线程安全的高性能通用缓存组件!

来源: 责编: 时间:2024-06-11 08:43:40 85观看
导读在实际工作中,有一种非常普遍的并发场景:那就是读多写少的场景。在这种场景下,为了优化程序的性能,我们经常使用缓存来提高应用的访问性能。因为缓存非常适合使用在读多写少的场景中。在并发场景中,Java SDK中提供了ReadWr

Ix228资讯网——每日最新资讯28at.com

在实际工作中,有一种非常普遍的并发场景:那就是读多写少的场景。在这种场景下,为了优化程序的性能,我们经常使用缓存来提高应用的访问性能。因为缓存非常适合使用在读多写少的场景中。Ix228资讯网——每日最新资讯28at.com

在并发场景中,Java SDK中提供了ReadWriteLock来满足读多写少的场景。本文我们就来说说使用ReadWriteLock如何实现一个通用的缓存中心。Ix228资讯网——每日最新资讯28at.com

本文涉及的知识点有:Ix228资讯网——每日最新资讯28at.com

Ix228资讯网——每日最新资讯28at.com

读写锁

说起读写锁,相信小伙伴们并不陌生。总体来说,读写锁需要遵循以下原则:Ix228资讯网——每日最新资讯28at.com

  • 一个共享变量允许同时被多个读线程读取到。
  • 一个共享变量在同一时刻只能被一个写线程进行写操作。
  • 一个共享变量在被写线程执行写操作时,此时这个共享变量不能被读线程执行读操作。

这里,需要小伙伴们注意的是:读写锁和互斥锁的一个重要的区别就是:读写锁允许多个线程同时读共享变量,而互斥锁不允许。所以,在高并发场景下,读写锁的性能要高于互斥锁。但是,读写锁的写操作是互斥的,也就是说,使用读写锁时,一个共享变量在被写线程执行写操作时,此时这个共享变量不能被读线程执行读操作。Ix228资讯网——每日最新资讯28at.com

读写锁支持公平模式和非公平模式,具体是在ReentrantReadWriteLock的构造方法中传递一个boolean类型的变量来控制。Ix228资讯网——每日最新资讯28at.com

public ReentrantReadWriteLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();    readerLock = new ReadLock(this);    writerLock = new WriteLock(this);}

另外,需要注意的一点是:在读写锁中,读锁调用newCondition()会抛出UnsupportedOperationException异常,也就是说:读锁不支持条件变量。Ix228资讯网——每日最新资讯28at.com

缓存实现

这里,我们使用ReadWriteLock快速实现一个缓存的通用工具类,总体代码如下所示。Ix228资讯网——每日最新资讯28at.com

public class ReadWriteLockCache<K,V> {    private final Map<K, V> 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并不是线程安全的类。Ix228资讯网——每日最新资讯28at.com

所以,这里使用了读写锁来保证线程的安全性,例如,我们在get()方法中使用了读锁,get()方法可以被多个线程同时执行读操作;put()方法内部使用写锁,也就是说,put()方法在同一时刻只能有一个线程对缓存进行写操作。Ix228资讯网——每日最新资讯28at.com

这里需要注意的是:无论是读锁还是写锁,锁的释放操作都需要放到finally{}代码块中。Ix228资讯网——每日最新资讯28at.com

在以往的经验中,有两种向缓存中加载数据的方式,一种是:项目启动时,将数据全量加载到缓存中,一种是在项目运行期间,按需加载所需要的缓存数据。Ix228资讯网——每日最新资讯28at.com

Ix228资讯网——每日最新资讯28at.com

接下来,我们就分别来看看全量加载缓存和按需加载缓存的方式。Ix228资讯网——每日最新资讯28at.com

全量加载缓存

全量加载缓存相对来说比较简单,就是在项目启动的时候,将数据一次性加载到缓存中,这种情况适用于缓存数据量不大,数据变动不频繁的场景,例如:可以缓存一些系统中的数据字典等信息。整个缓存加载的大体流程如下所示。Ix228资讯网——每日最新资讯28at.com

Ix228资讯网——每日最新资讯28at.com

将数据全量加载到缓存后,后续就可以直接从缓存中读取相应的数据了。Ix228资讯网——每日最新资讯28at.com

全量加载缓存的代码实现比较简单,这里,我就直接使用如下代码进行演示。Ix228资讯网——每日最新资讯28at.com

public class ReadWriteLockCache<K,V> {    private final Map<K, V> m = new HashMap<>();    private final ReadWriteLock rwl = new ReentrantReadWriteLock();    // 读锁    private final Lock r = rwl.readLock();    // 写锁    private final Lock w = rwl.writeLock();        public ReadWriteLockCache(){        //查询数据库        List<Field<K, V>> 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(); }    }}

按需加载缓存

按需加载缓存也可以叫作懒加载,就是说:需要加载的时候才会将数据加载到缓存。具体来说:就是程序启动的时候,不会将数据加载到缓存,当运行时,需要查询某些数据,首先检测缓存中是否存在需要的数据,如果存在,则直接读取缓存中的数据,如果不存在,则到数据库中查询数据,并将数据写入缓存。后续的读取操作,因为缓存中已经存在了相应的数据,直接返回缓存的数据即可。Ix228资讯网——每日最新资讯28at.com

Ix228资讯网——每日最新资讯28at.com

这种查询缓存的方式适用于大多数缓存数据的场景。Ix228资讯网——每日最新资讯28at.com

我们可以使用如下代码来表示按需查询缓存的业务。Ix228资讯网——每日最新资讯28at.com

class ReadWriteLockCache<K,V> {    private final Map<K, V> 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()方法中,首先从缓存中读取数据,此时,我们对查询缓存的操作添加了读锁,查询返回后,进行解锁操作。判断缓存中返回的数据是否为空,不为空,则直接返回数据;如果为空,则获取写锁,之后再次从缓存中读取数据,如果缓存中不存在数据,则查询数据库,将结果数据写入缓存,释放写锁。最终返回结果数据。Ix228资讯网——每日最新资讯28at.com

这里,有小伙伴可能会问:为啥程序都已经添加写锁了,在写锁内部为啥还要查询一次缓存呢?Ix228资讯网——每日最新资讯28at.com

这是因为在高并发的场景下,可能会存在多个线程来竞争写锁的现象。例如:第一次执行get()方法时,缓存中的数据为空。如果此时有三个线程同时调用get()方法,同时运行到 w.lock()代码处,由于写锁的排他性。此时只有一个线程会获取到写锁,其他两个线程则阻塞在w.lock()处。获取到写锁的线程继续往下执行查询数据库,将数据写入缓存,之后释放写锁。Ix228资讯网——每日最新资讯28at.com

此时,另外两个线程竞争写锁,某个线程会获取到锁,继续往下执行,如果在w.lock()后没有 v = m.get(key); 再次查询缓存的数据,则这个线程会直接查询数据库,将数据写入缓存后释放写锁。最后一个线程同样会按照这个流程执行。Ix228资讯网——每日最新资讯28at.com

这里,实际上第一个线程已经查询过数据库,并且将数据写入缓存了,其他两个线程就没必要再次查询数据库了,直接从缓存中查询出相应的数据即可。Ix228资讯网——每日最新资讯28at.com

所以,在w.lock()后添加 v = m.get(key); 再次查询缓存的数据,能够有效的减少高并发场景下重复查询数据库的问题,提升系统的性能。Ix228资讯网——每日最新资讯28at.com

读写锁的升降级

关于锁的升降级,小伙伴们需要注意的是:在ReadWriteLock中,锁是不支持升级的,因为读锁还未释放时,此时获取写锁,就会导致写锁永久等待,相应的线程也会被阻塞而无法唤醒。Ix228资讯网——每日最新资讯28at.com

虽然不支持锁升级,但是ReadWriteLock支持锁降级,例如,我们来看看官方的ReentrantReadWriteLock示例,如下所示。Ix228资讯网——每日最新资讯28at.com

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

数据同步问题

首先,这里说的数据同步指的是数据源和数据缓存之间的数据同步,说的再直接一点,就是数据库和缓存之间的数据同步。Ix228资讯网——每日最新资讯28at.com

这里,我们可以采取三种方案来解决数据同步的问题,如下图所示:Ix228资讯网——每日最新资讯28at.com

Ix228资讯网——每日最新资讯28at.com

超时机制

这个比较好理解,就是在向缓存写入数据的时候,给一个超时时间,当缓存超时后,缓存的数据会自动从缓存中移除,此时程序再次访问缓存时,由于缓存中不存在相应的数据,查询数据库得到数据后,再将数据写入缓存。Ix228资讯网——每日最新资讯28at.com

定时更新缓存

这种方案是超时机制的增强版,在向缓存中写入数据的时候,同样给一个超时时间。与超时机制不同的是,在程序后台单独启动一个线程,定时查询数据库中的数据,然后将数据写入缓存中,这样能够在一定程度上避免缓存的穿透问题。Ix228资讯网——每日最新资讯28at.com

实时更新缓存

这种方案能够做到数据库中的数据与缓存的数据是实时同步的,可以使用阿里开源的Canal框架实现MySQL数据库与缓存数据的实时同步。也可以使用我个人开源的mykit-data框架哦(推荐使用)~~Ix228资讯网——每日最新资讯28at.com

mykit-data开源地址:Ix228资讯网——每日最新资讯28at.com

  • https://github.com/sunshinelyz/mykit-data。
  • https://gitee.com/binghe001/mykit-data。

本文链接:http://www.28at.com/showinfo-26-92928-0.html十分钟手撸一款线程安全的高性能通用缓存组件!

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Oh-My-Posh: 可定制且低延迟的跨平台/跨Shell提示符渲染器

下一篇: 前端轮播图,搞定!

标签:
  • 热门焦点
  • 红魔电竞平板评测:大屏幕硬实力

    红魔电竞平板评测:大屏幕硬实力

    前言:三年的疫情因为要上网课的原因激活了平板市场,如今网课的时代已经过去,大家的生活都恢复到了正轨,这也就意味着,真正考验平板电脑生存的环境来了。也就是面对着这种残酷的
  • Rust中的高吞吐量流处理

    Rust中的高吞吐量流处理

    作者 | Noz编译 | 王瑞平本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序
  • 只需五步,使用start.spring.io快速入门Spring编程

    只需五步,使用start.spring.io快速入门Spring编程

    步骤1打开https://start.spring.io/,按照屏幕截图中的内容创建项目,添加 Spring Web 依赖项,并单击“生成”按钮下载 .zip 文件,为下一步做准备。请在进入步骤2之前进行解压。图
  • 花7万退货退款无门:谁在纵容淘宝珠宝商家造假?

    花7万退货退款无门:谁在纵容淘宝珠宝商家造假?

    来源:极点商业作者:杨铭在淘宝购买珠宝玉石后,因为保证金不够赔付,店铺关闭,退货退款难、维权无门的比比皆是。&ldquo;提供相关产品鉴定证书,支持全国复检,可以30天无理由退换货。&
  • 认真聊聊东方甄选:如何告别低垂的果实

    认真聊聊东方甄选:如何告别低垂的果实

    来源:山核桃作者:财经无忌爆火一年后,俞敏洪和他的东方甄选依旧是颇受外界关心的&ldquo;网红&rdquo;。7月5日至9日,为期5天的东方甄选&ldquo;甘肃行&rdquo;首次在自有App内直播,
  • 华为发布HarmonyOS 4:更好玩、更流畅、更安全

    华为发布HarmonyOS 4:更好玩、更流畅、更安全

    在8月4日的华为开发者大会2023(HDC.Together)大会上,HarmonyOS 4正式发布。自2019年发布以来,HarmonyOS一直以用户为中心,经历四年多的发展HarmonyOS已
  • 三星折叠屏手机去年销售近1000万台 今年目标定为1500万

    三星折叠屏手机去年销售近1000万台 今年目标定为1500万

    7月29日消息,三星率先发力可折叠手机市场,在全球市场已经取得了非常亮眼的成绩,接下来会进一步巩固和扩大这一优势。三星在推出Galaxy Z Flip5和Galax
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
  • 北京:科技教育体验基地开始登记

    北京:科技教育体验基地开始登记

      北京“科技馆之城”科技教育体验基地登记和认证工作日前启动。首批北京科技教育体验基地拟于2023年全国科普日期间挂牌,后续还将开展常态化登记。  北京科技教育体验基
Top