Java并发编程基础构建模块(06)——高效缓存总结示例

        前面介绍完了并发编程的基础构建模块,也就是一些常用的基础类,这里做个简单的例子,很常见的一个功能,缓存,缓存通常情况下看上去比较简单,实际中各种缓存框架已经让我们开发非常方便,但是假如自己新写一个缓存功能呢,是否能做到高效且可伸缩呢?

       下面做一个简单缓存,用于改进一个计算费时且复杂的函数。先看一下原始功能:

        

        由于这个工具计算方法传入相同参数的结果也相同(在数学中是普遍的现象,其他情况下不一定),所以可将计算结果缓存起来(放到内存中,也就是放到某个属性里面存着),后续再用到的时候就不用重新计算了。首先想到的是放到一个Map中,结果如下:

        

        因为HashMap不是线程安全的,所以采用了比较保守的方法,整个方法都加上同步了,但这样就带来1个问题,同时只有1个线程能执行计算方法,其他线程都只能阻塞,计算方法时间较长,其他线程阻塞时间就很长。显然不是我们希望得到的结果,我们需要改进一下:

        (某些情况下确实可以,比如要计算的参数种类非常少,短时间内就能计算出全部参数的结果了,以后不会有新的参数了,这样前期慢点,后续就没问题了)

        

        上面这段代码,使用了ConcurrentHashMap消除了HashMap线程不安全的方法,也取消了synchronized同步,性能上来了,多线程并发使用也没问题了。但还有一个不足,当两个线程同时传入相同的参数时,会导致重复执行(业务逻辑执行时间越长概率越大),我们希望的结果是相同参数只计算1次,后续再调用就从缓存中获取就行了。

        这样就会引出一个问题,想要只计算1次,多线程还要同时获取这个结果怎么办,不由得想到前面介绍过的一个类:FutureTask。FutureTask线程在计算时,所有其他线程使用get方法获取值会阻塞,一旦计算完成,其他线程能马上拿到结果,好,我们改造一下:

        

        这样看似不错了,但也没彻底解决同一个值只执行1次,只是将原来的概率降的非常低而已,假如realCalculate要执行2s,Calculate02中2s内进来的相同方法会都执行,而Calculate03中把时间降到了if(ft== null)中的方法执行时间,也就是说相同值同时执行时,可能同时初始化多个FutureTask,虽然概率很低。防止重复初始化我们自然想到了加锁,看下面的代码:

        

        这个锁有点类似单例模式中的单例初始化的锁,好了,截至目前看似比较完美了,其实仍然有两处不足:

        1、 在创建FutureTask前加锁,锁的粒度比较大,同步功能越少对性能越好,其实还有更细的加锁方式,就是使用ConcurrentHashMap内自己的锁;

        2、 FutureTask存在计算失败的可能,如果计算线程被取消了怎么办,其他等待线程和后续的线程再执行时,这个参数对应的FutureTask已经损坏了,无法拿到结果了,所以当失败时,需要清除FutureTask,换一个新的。

        好,我们来看下面这个代码:

        

        这样看来就完美一些了,这样不仅高效,而且安全性也非常好,扩展性也不错。

        其实,再缓存设计中,如果不缓存最终结果,就要控制好缓存对象,否则会造成缓存污染问题,比如上面的程序中,没缓存最终结果,而是缓存了FutureTask,FutureTask会引来一些其他问题,比如发生某些异常时,取消计算时等情况要清除缓存,这样缓存才能计算成功。还有缓存逾期问题,每个结果需要指定一个时间,定期扫描逾期元素并删除等,不过这些是缓存设计方法考虑的,此处例子以并发为主,不做太多讨论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值