点击上方 好好学java ,选择 星标 公众号

重磅资讯、干货,第一时间送达
今日推荐:分享一个牛逼的 Java 开源后台管理系统,不要造轮子了!个人原创+1博客:点击前往,查看更多
作者:flydean
原文:https://segmentfault.com/a/1190000022237396

在java中构建高效的结果缓存

缓存是现代应用服务器中非常常用的组件。除了第三方缓存以外,我们通常也需要在java中构建内部使用的缓存。那么怎么才能构建一个高效的缓存呢? 本文将会一步步的进行揭秘。

使用HashMap

缓存通常的用法就是构建一个内存中使用的Map,在做一个长时间的操作比如计算之前,先在Map中查询一下计算的结果是否存在,如果不存在的话再执行计算操作。

我们定义了一个代表计算的接口:

public interface Calculator<A, V> {V calculate(A arg) throws InterruptedException;
}

该接口定义了一个calculate方法,接收一个参数,并且返回计算的结果。

我们要定义的缓存就是这个Calculator具体实现的一个封装。

我们看下用HashMap怎么实现:

public class MemoizedCalculator1<A, V> implements Calculator<A, V> {private final Map<A, V> cache= new HashMap<A, V>();private final Calculator<A, V> calculator;public MemoizedCalculator1(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic synchronized V calculate(A arg) throws InterruptedException {V result= cache.get(arg);if( result ==null ){result= calculator.calculate(arg);cache.put(arg, result);}return result;}
}

MemoizedCalculator1封装了Calculator,在调用calculate方法中,实际上调用了封装的Calculator的calculate方法。

因为HashMap不是线程安全的,所以这里我们使用了synchronized关键字,从而保证一次只有一个线程能够访问calculate方法。

虽然这样的设计能够保证程序的正确执行,但是每次只允许一个线程执行calculate操作,其他调用calculate方法的线程将会被阻塞,在多线程的执行环境中这会严重影响速度。从而导致使用缓存可能比不使用缓存需要的时间更长。

使用ConcurrentHashMap

因为HashMap不是线程安全的,那么我们可以尝试使用线程安全的ConcurrentHashMap来替代HashMap。如下所示:

public class MemoizedCalculator2<A, V> implements Calculator<A, V> {private final Map<A, V> cache= new ConcurrentHashMap<>();private final Calculator<A, V> calculator;public MemoizedCalculator2(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic V calculate(A arg) throws InterruptedException {V result= cache.get(arg);if( result ==null ){result= calculator.calculate(arg);cache.put(arg, result);}return result;}
}

上面的例子中虽然解决了之前的线程等待的问题,但是当有两个线程同时在进行同一个计算的时候,仍然不能保证缓存重用,这时候两个线程都会分别调用计算方法,从而导致重复计算。

我们希望的是如果一个线程正在做计算,其他的线程只需要等待这个线程的执行结果即可。很自然的,我们想到了之前讲到的FutureTask。FutureTask表示一个计算过程,我们可以通过调用FutureTask的get方法来获取执行的结果,如果该执行正在进行中,则会等待。

下面我们使用FutureTask来进行改写。

FutureTask

@Slf4j
public class MemoizedCalculator3<A, V> implements Calculator<A, V> {private final Map<A, Future<V>> cache= new ConcurrentHashMap<>();private final Calculator<A, V> calculator;public MemoizedCalculator3(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic V calculate(A arg) throws InterruptedException {Future<V> future= cache.get(arg);V result=null;if( future ==null ){Callable<V> callable= new Callable<V>() {@Overridepublic V call() throws Exception {return calculator.calculate(arg);}};FutureTask<V> futureTask= new FutureTask<>(callable);future= futureTask;cache.put(arg, futureTask);futureTask.run();}try {result= future.get();} catch (ExecutionException e) {log.error(e.getMessage(),e);}return result;}
}

上面的例子,我们用FutureTask来封装计算,并且将FutureTask作为Map的value。

上面的例子已经体现了很好的并发性能。但是因为if语句是非原子性的,所以对这一种先检查后执行的操作,仍然可能存在同一时间调用的情况。

这个时候,我们可以借助于ConcurrentHashMap的原子性操作putIfAbsent来重写上面的类:

@Slf4j
public class MemoizedCalculator4<A, V> implements Calculator<A, V> {private final Map<A, Future<V>> cache= new ConcurrentHashMap<>();private final Calculator<A, V> calculator;public MemoizedCalculator4(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic V calculate(A arg) throws InterruptedException {while (true) {Future<V> future = cache.get(arg);V result = null;if (future == null) {Callable<V> callable = new Callable<V>() {@Overridepublic V call() throws Exception {return calculator.calculate(arg);}};FutureTask<V> futureTask = new FutureTask<>(callable);future = cache.putIfAbsent(arg, futureTask);if (future == null) {future = futureTask;futureTask.run();}try {result = future.get();} catch (CancellationException e) {log.error(e.getMessage(), e);cache.remove(arg, future);} catch (ExecutionException e) {log.error(e.getMessage(), e);}return result;}}}
}

上面使用了一个while循环,来判断从cache中获取的值是否存在,如果不存在则调用计算方法。

上面我们还要考虑一个缓存污染的问题,因为我们修改了缓存的结果,如果在计算的时候,计算被取消或者失败,我们需要从缓存中将FutureTask移除。

本文的例子可以参考https://github.com/ddean2009/learn-java-concurrency/tree/master/MemoizedCalculate

自己构建一个高效缓存服务!相关推荐

  1. Java趣谈——如何构建一个高效且可伸缩的缓存

    Java趣谈--如何构建一个高效且可伸缩的缓存 本集概要: 怎样构建一个线程安全而又高效.可伸缩的缓存? 怎样利用设计模式,把缓存做成通用的工具? 除了synchronize和volatile,我们还 ...

  2. 如何构建一个有效的服务治理平台

    本文我们重点讨论如何构建一个有效的服务治理平台,话不多说,直接切入整体.构建服务治理平台基于"管理","度量","管控"三个层面统筹考虑安排 ...

  3. 如何构建一个高效的企业舆情监测系统?

    随着互联网的快速发展,企业需要对自身在网络上的形象和声誉进行全方位的监控和管理,以保证企业的长期稳定发展.构建一个高效的企业舆情监测系统已经成为了当下企业发展的必要手段.本文将通过国内具体案例分析,阐 ...

  4. 如何构建一个高效且可伸缩的缓存

    本集概要: 怎样构建一个线程安全而又高效.可伸缩的缓存? 怎样利用设计模式,把缓存做成通用的工具? 除了synchronize和volatile,我们还能使用哪些工具来开发线程安全的代码? 一.糙版缓 ...

  5. java redis 缓存_如何在 Java 中实现一个 redis 缓存服务

    缓存服务的意义 为什么要使用缓存?说到底是为了提高系统的运行速度.将用户频繁访问的内容存放在离用户最近,访问速度最快的地方,提高用户的响应速度.一个 web 应用的简单结构如下图. web 应用典型架 ...

  6. 用简单的方法构建一个高可用服务端

    2019独角兽企业重金招聘Python工程师标准>>> 一. 什么是高可用性 服务端,顾名思义就是为用户提供服务的. 停工时间,就是不能向用户提供服务的时间. 高可用,就是系统具有高 ...

  7. SpringBoot 精通系列-构建一个RESTful Web 服务

    导语   现在越来越多的企业推荐使用的是RESTful风格来构建企业应用接口,那么什么是RESTful呢? 文章目录 什么是RESTful SpringBoot对于RESTful有哪些支持 快速实例 ...

  8. 如何在 15 分钟内构建一个无服务器服务?

    "无服务器"(Serverless)这个词已经流行了有一段时间了. 亚马逊在2015年发布了AWS Lambda服务之后,出现了许多工具,利用这些工具只需几个命令就可以建个无服务器 ...

  9. centos dns服务器_用 OpenStack Designate 构建一个 DNS 即服务(DNSaaS) | Linux 中国

    学习如何安装和配置 Designate,这是一个 OpenStack 的多租户 DNS 即服务(DNSaaS).-- Amjad Yaseen Designate 是一个多租户的 DNS 即服务,它包 ...

最新文章

  1. tomcat 连接oracle重连,JSP+Tomcat连接Oracle数据库
  2. 16.PHP_Ajax模拟服务器登录验证
  3. 分类VS标签,一文带你看懂数据中台为什么要建标签体系?
  4. Java小结(四)——折半查找、选择排序、冒泡排序
  5. jzoj3794,P1383-高级打字机【欧拉序,离线O(n)】
  6. 合并工具_你值得拥有这个PDF合并工具 免费获取转换方法
  7. 10060 mysql_navicat连接mysql服务端报10060错误解决过程如下
  8. MathType 在Word中的应用
  9. git的一些简单使用
  10. 鸿蒙公测第二期报名地址,鸿蒙2.0第二期公测报名入口及参加方法
  11. 实践出真知--ZAC《网络营销实战密码》
  12. 设计模式练习:Composite模式
  13. matlab傅里叶变换程序
  14. 土地调查图斑编号_全国第二次土地调查地类分类图示及图斑.doc
  15. 什么是DNS云解析?云解析和普通解析有什么区别?
  16. 策略模式:网络小说的固定套路
  17. 四分位数计算方法总结
  18. UVALive - 4636 Cubist Artwork——思维
  19. PS改变背景图片/颜色(3种方法)
  20. 计蒜客--蒜头君回家

热门文章

  1. 做人,你想过这四个致命的问题吗
  2. stm32中断向量控制器
  3. kmalloc/kfree,vmalloc/vfree函数用法和区别
  4. 数据挖掘 —— 数据预处理
  5. 深度学习概览之自然语言处理:从基本概念到前沿研究
  6. c++新特性11 (10)shared_ptr一”概述“
  7. STL中的priority_queue(优先队列)
  8. 各个级别镜像之间的跳转模型
  9. unittest单元测试框架总结
  10. php奇数乘法表,PHP九九乘法表