热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Java构建高效结果缓存方法示例

这篇文章主要介绍了Java构建高效结果缓存方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

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

使用HashMap

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

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

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

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

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

我们看下用HashMap怎么实现:

public class MemoizedCalculator1 implements Calculator {

  private final Map cache= new HashMap();
  private final Calculator calculator;
  public MemoizedCalculator1(Calculator calculator){
    this.calculator=calculator;
  }
  @Override
  public 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 implements Calculator {

  private final Map cache= new ConcurrentHashMap<>();
  private final Calculator calculator;
  public MemoizedCalculator2(Calculator calculator){
    this.calculator=calculator;
  }
  @Override
  public 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 implements Calculator {

  private final Map> cache= new ConcurrentHashMap<>();
  private final Calculator calculator;

  public MemoizedCalculator3(Calculator calculator){
    this.calculator=calculator;
  }
  @Override
  public V calculate(A arg) throws InterruptedException {
    Future future= cache.get(arg);
    V result=null;
    if( future ==null ){
      Callable callable= new Callable() {
        @Override
        public V call() throws Exception {
          return calculator.calculate(arg);
        }
      };
      FutureTask 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 implements Calculator {

  private final Map> cache= new ConcurrentHashMap<>();
  private final Calculator calculator;

  public MemoizedCalculator4(Calculator calculator){
    this.calculator=calculator;
  }
  @Override
  public V calculate(A arg) throws InterruptedException {
    while (true) {
      Future future = cache.get(arg);
      V result = null;
      if (future == null) {
        Callable callable = new Callable() {
          @Override
          public V call() throws Exception {
            return calculator.calculate(arg);
          }
        };
        FutureTask 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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • REST与RPC:选择哪种API架构风格?
    在探讨REST与RPC这两种API架构风格的选择时,本文首先介绍了RPC(远程过程调用)的概念。RPC允许客户端通过网络调用远程服务器上的函数或方法,从而实现分布式系统的功能调用。相比之下,REST(Representational State Transfer)则基于资源的交互模型,通过HTTP协议进行数据传输和操作。本文将详细分析两种架构风格的特点、适用场景及其优缺点,帮助开发者根据具体需求做出合适的选择。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • 深入解析HTTPS:保障Web安全的加密协议
    本文详细探讨了HTTPS协议在保障Web安全中的重要作用。首先分析了HTTP协议的不足之处,包括数据传输过程中的安全性问题和内容加密的缺失。接着介绍了HTTPS如何通过使用公钥和私钥的非对称加密技术以及混合加密机制,确保数据的完整性和机密性。最后强调了HTTPS的安全性和可靠性,为现代网络通信提供了坚实的基础。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 神经元研究动态:城市大脑标准化体系及评估指标综合框架分析
    神经元研究动态:城市大脑标准化体系及评估指标综合框架分析 ... [详细]
  • CTF竞赛中文件上传技巧与安全绕过方法深入解析
    CTF竞赛中文件上传技巧与安全绕过方法深入解析 ... [详细]
  • 本文介绍了 Vue 开发的入门指南,重点讲解了开发环境的配置与项目的基本搭建。推荐使用 WebStorm 作为 IDE,其下载地址为 。安装时请选择适合您操作系统的版本,并通过 获取激活码。WebStorm 是前端开发者的理想选择,提供了丰富的功能和强大的代码编辑能力。 ... [详细]
  • 每日前端实战:148# 视频教程展示纯 CSS 实现按钮两侧滑入装饰元素的悬停效果
    通过点击页面右侧的“预览”按钮,您可以直接在当前页面查看效果,或点击链接进入全屏预览模式。该视频教程展示了如何使用纯 CSS 实现按钮两侧滑入装饰元素的悬停效果。视频内容具有互动性,观众可以实时调整代码并观察变化。访问以下链接体验完整效果:https://codepen.io/comehope/pen/yRyOZr。 ... [详细]
  • 将密码链接至密码输入框中以增强安全性
    在通常情况下,当TextBox的TextMode设置为Password时,直接在后台(.cs文件)绑定值到该文本框是不可行的。然而,在某些场景下,为了增强安全性,需要将密码值安全地传递到密码输入框中。本文介绍了一种方法,通过使用特定的技术手段,实现后台与前端密码输入框的安全绑定,从而提高系统的整体安全性。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 具备括号和分数功能的高级四则运算计算器
    本研究基于C语言开发了一款支持括号和分数运算的高级四则运算计算器。该计算器通过模拟手算过程,对每个运算符进行优先级标记,并按优先级从高到低依次执行计算。其中,加减运算的优先级最低,为0。此外,该计算器还支持复杂的分数运算,能够处理包含括号的表达式,提高了计算的准确性和灵活性。 ... [详细]
  • MySQL 8.0 MGR 自动化部署与配置:DBA 和开源工具的高效解决方案
    MySQL 8.0 MGR 自动化部署与配置:DBA 和开源工具的高效解决方案 ... [详细]
  • 在 CentOS 7 中,为了扩展可用软件包的数量,通常需要配置多个第三方软件源。这些第三方源包括 EPEL、Nux Dextop 和 ELRepo 等,它们提供了大量官方源中未包含的软件包,从而增强了系统的功能性和灵活性。通过正确配置这些源,用户可以轻松安装和管理更多种类的软件,满足不同的需求。 ... [详细]
  • 在日常的项目开发中,测试环境和生产环境通常采用HTTP协议访问服务。然而,从浏览器的角度来看,这种访问方式会被标记为不安全。为了提升安全性,当前大多数生产环境已经转向了HTTPS协议。本文将详细介绍如何在Spring Boot应用中配置SSL证书,以实现HTTPS安全访问。通过这一过程,不仅可以增强数据传输的安全性,还能提高用户对系统的信任度。 ... [详细]
  • 在使用 SQL Server 时,连接故障是用户最常见的问题之一。通常,连接 SQL Server 的方法有两种:一种是通过 SQL Server 自带的客户端工具,例如 SQL Server Management Studio;另一种是通过第三方应用程序或开发工具进行连接。本文将详细分析导致连接故障的常见原因,并提供相应的解决策略,帮助用户有效排除连接问题。 ... [详细]
author-avatar
YYANNILl_242
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有