摘抄自java并发实战


有时候需要对数据缓存。用Map缓存数据比较合适。但是由于对吞吐量,一致性,计算性能的要求,对数据进行缓存的设计还是需要慎重考虑的。

一、利用HashMap加同步

(1)说明

把HashMap当作缓存容器。每缓存一个key的时候,都进行同步。

(2)代码

  1. package memory;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. /**
  7. * Created by adrian.wu on 2018/12/12.
  8. */
  9. public class MemoryFirst<K, V> implements Computable<K, V> {
  10. private final Map<K, V> cache = new HashMap<>();
  11. private final Computable<K, V> c;
  12.  
  13. public MemoryFirst(Computable<K, V> c) {
  14. this.c = c;
  15. }
  16.  
  17. @Override
  18. public synchronized V compute(K arg) throws InterruptedException {
  19. V result = cache.get(arg);
  20.  
  21. if (result == null) {
  22. result = c.compute(arg);
  23. cache.put(arg, result);
  24. }
  25.  
  26. return result;
  27. }
  28. }

(3)缺点

由于HashMap并非线程安全,因此每一次计算都使用同步机制确保线程安全。很明显,这种方式伸缩性比较差。因为一个线程正在计算结果,其它所有线程都在等待,即使对应的arg是不同的。

二、用ConcurrentHashMap代替HashMap

(1)说明

ConcurrentHashMap是线程安全的,并且同步并非对整个Map进行同步而是对每一个分段进行同步,所以并发性也可以大大提升。

(2)代码

  1. package memory;
  2.  
  3. import java.util.Map;
  4. import java.util.concurrent.ConcurrentHashMap;
  5.  
  6. /**
  7. * Created by adrian.wu on 2018/12/12.
  8. */
  9. public class MemorySecond<K, V> implements Computable<K, V> {
  10. private final Map<K, V> cache = new ConcurrentHashMap<>();
  11. private final Computable<K, V> c;
  12.  
  13. public MemorySecond(Computable<K, V> c) {
  14. this.c = c;
  15. }
  16.  
  17. @Override
  18. public V compute(K arg) throws InterruptedException {
  19. V result = cache.get(arg);
  20.  
  21. if (result == null) {
  22. result = c.compute(arg);
  23. cache.put(arg, result);
  24. }
  25.  
  26. return result;
  27. }
  28. }

(3)缺点

相比第一个设计方案。这种方案已经有很大的提升了。但是如果一个compute的计算开销很大,恰巧有另一个同一个arg的线程同时请求compute,则会造成重复计算,重复put的情况。所以我们希望如果有一个线程正在计算的时候另一个线程正在等待而不是重复计算。

三、利用FutureTask解决第二个设计的问题

(1)说明

利用FutrueTask, 如果get到结果则返回,如果正在计算则利用FutureTask的特性阻塞。否则计算。

(2)代码

  1. package memory;
  2.  
  3. import org.slf4j.Logger;
  4.  
  5. import java.util.Map;
  6. import java.util.concurrent.*;
  7.  
  8. import static memory.ErrorHandler.launderThrowable;
  9.  
  10. /**
  11. * Created by adrian.wu on 2018/12/12.
  12. */
  13. public class MemoryThird<K, V> implements Computable<K, V> {
  14. private final Map<K, Future<V>> cache = new ConcurrentHashMap<>();
  15. private final Computable<K, V> c;
  16.  
  17. public MemoryThird(Computable<K, V> c) {
  18. this.c = c;
  19. }
  20.  
  21. @Override
  22. public V compute(final K arg) throws InterruptedException {
  23. Future<V> f = cache.get(arg);
  24. if (f == null) {
  25. Callable<V> eval = new Callable<V>() {
  26. @Override
  27. public V call() throws Exception {
  28. return c.compute(arg);
  29. }
  30. };
  31.  
  32. FutureTask<V> ft = new FutureTask<>(eval);
  33. f = ft;
  34. cache.put(arg, ft);
  35. ft.run(); // start compute
  36. }
  37. try {
  38. return f.get();
  39. } catch (ExecutionException e) {
  40. throw launderThrowable(e.getCause());
  41. }
  42. }
  43. }

(3)缺点

只有一个缺陷,仍然存在两个线程计算出相同值的漏洞。就是由于compute方法中的if代码块仍然是非原子的“先检查,再执行”,因此仍然有可能两个线程在同一时间计算一个不存在的arg。原因是第23行的get方法和34行的put方法是对底层的Map操作,所以无法保证原子性。由于cache里面的是future而不是真正的值,所以将有可能导致缓存污染(cache pollution)问题,即如果某个计算过程被取消或者失败,那么缓存存入的Future是有缺陷的。

四、最终设计方案

(1)说明

使用putIfAbsent代替put,以保证原子性。如果发现Future计算被取消或失败则删除,从而缓存不会消耗过多内存。

(2)代码

  1. package memory;
  2.  
  3. import java.util.Map;
  4. import java.util.concurrent.*;
  5.  
  6. import static memory.ErrorHandler.launderThrowable;
  7.  
  8. /**
  9. * Created by adrian.wu on 2018/12/12.
  10. */
  11. public class Memory<K, V> implements Computable<K, V> {
  12. private Map<K, Future<V>> cache = new ConcurrentHashMap<>();
  13. private Computable<K, V> c;
  14.  
  15. public Memory(Computable<K, V> c) {
  16. this.c = c;
  17. }
  18.  
  19. @Override
  20. public V compute(K arg) throws InterruptedException {
  21. while (true) {
  22. Future<V> f = cache.get(arg);
  23.  
  24. if (f == null) {
  25. Callable<V> eval = new Callable<V>() {
  26. @Override
  27. public V call() throws Exception {
  28. return c.compute(arg);
  29. }
  30. };
  31.  
  32. FutureTask<V> ft = new FutureTask<>(eval);
  33.  
  34. f = cache.putIfAbsent(arg, ft); //double check
  35. if (f == null) {
  36. f = ft;
  37. ft.run(); //start compute
  38. }
  39. }
  40. try {
  41. return f.get();
  42. } catch (CancellationException e) {
  43. cache.remove(arg);
  44. } catch (ExecutionException e) {
  45. throw launderThrowable(e.getCause());
  46. }
  47. }
  48. }
  49. }

java实现数据缓存的更多相关文章

  1. java——包装类数据缓存 ==号详解

    Java对部分经常使用的数据采用缓存技术,即第一次使用该数据则创建该数据对象并对其进行缓存, 当再次使用等值对象时直接从缓存中获取,从而提高了程序执行性能.(只对常用数据进行缓存) Java中只是对部 ...

  2. java之ibatis数据缓存

    使用IBatis作数据缓存 1.SqlMapConfig.xml中<settingscacheModelsEnabled="true" //设置为trueenhancemen ...

  3. Java EE数据持久化框架 • 【第5章 MyBatis代码生成器和缓存配置】

    全部章节   >>>> 本章目录 5.1 配置MyBatis Generator 5.1.1 MyBatis Generator介绍 5.1.2 MyBatis Generat ...

  4. 「小程序JAVA实战」小程序数据缓存API(54)

    转自:https://idig8.com/2018/09/22/xiaochengxujavashizhanxiaochengxushujuhuancunapi52/ 刚开始写小程序的时候,用户信息我 ...

  5. jQuery 2.0.3 源码分析 数据缓存

    历史背景: jQuery从1.2.3版本引入数据缓存系统,主要的原因就是早期的事件系统 Dean Edwards 的 ddEvent.js代码 带来的问题: 没有一个系统的缓存机制,它把事件的回调都放 ...

  6. Android清除本地数据缓存代码案例

    Android清除本地数据缓存代码案例 直接上代码: /*  * 文 件 名:  DataCleanManager.java  * 描    述:  主要功能有清除内/外缓存,清除数据库,清除shar ...

  7. SpringMVC + ehcache( ehcache-spring-annotations)基于注解的服务器端数据缓存

    背景 声明,如果你不关心java缓存解决方案的全貌,只是急着解决问题,请略过背景部分. 在互联网应用中,由于并发量比传统的企业级应用会高出很多,所以处理大并发的问题就显得尤为重要.在硬件资源一定的情况 ...

  8. mybatis存取blob对象+@Cacheable实现数据缓存

    参考文档: http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 需求场景: 当前业务通过第三方接口查询一个业务数据, ...

  9. Eclipse rap 富客户端开发总结(14) :rap 图片、数据缓存处理

    一.概述 在进行了 rap 的基本学习之后,您对 rap 的理解是否进入了更高的一个层次呢,个人觉得,对学习 rap 的人来说,进行 rap 的学习是一个探索的过程,在编程中不断的对其进行理解和分析, ...

随机推荐

  1. Tbox在整车CAN网络的位置与作用

    我们讲到了智能车载娱乐系统的5个基本特征: 基本来说, 当今的智能车机基本有以下几个特点: 基于智能操作系统: Android, Yunos, Linux等 基本都是虚拟按键, 较少用实体按键 具备外 ...

  2. CAN协议,系统结构和帧结构

    CAN:Controller Area Network,控制器局域网 是一种能有效支持分布式控制和实时控制的串行通讯网络. CAN-bus: Controller Area Network-bus,控 ...

  3. FZU 2150 Fire Game (bfs+dfs)

    Problem Description Fat brother and Maze are playing a kind of special (hentai) game on an N*M board ...

  4. js 获取当前时间 年月日

    var datetime = new Date(); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1 &l ...

  5. BZOJ2288 生日礼物

    本题是数据备份的进阶版. 首先去掉所有0,把连续的正数/负数连起来. 计算所有正数段的个数与总和. 然后考虑数据备份,有一点区别: 如果我们在数列中选出一个负数,相当于把它左右连起来. 选出一个正数, ...

  6. 2019-1-17 script(1)

    伪终端(Pseudo Terminal)是成对的逻辑终端设备. grant  授予 tty是teletype(电传打字机)的缩写,后来便成了终端设备的代名词 虚拟终端pty(pseudo-tty) p ...

  7. POJ 1659 Frogs' Neighborhood (Havel--Hakimi定理)

    Frogs' Neighborhood Time Limit: 5000MS   Memory Limit: 10000K Total Submissions: 10545   Accepted: 4 ...

  8. pycharm更新之后pip显示没有main

    更新pip之后,Pycharm安装package出现报错:module 'pip' has no attribute 'main' 找到安装目录下 helpers/packaging_tool.py文 ...

  9. TRAFFIC ANALYSIS EXERCISE - Ransomer

    catalogue . SCENARIO . QUESTIONS . Analysis:10.3.14.134 . Analysis:10.3.14.131 1. SCENARIO The pcap ...

  10. win10 同步批处理禁用和启用网卡

    @ echo off echo 正在启用超级管理员权限... %1 %2 ver|find "5.">nul&&goto :st mshta vbscript ...