建立高速缓存机制-java版
前言
一台计算机的核心是CPU,它是计算机系统的运算和控制核心。由于它处理运算速度快,所以基本都会给CPU配置一级缓存,当CPU要读取一个数据时,首先从缓存中查询,如果没有在从内存或者磁盘块中找。
同样的,作为一个服务器应用程序,为了让应用程序运行更快速,响应更给力,我们会给它设置一些数据缓存,这样可以提高应用程序的吞吐量、缩短客户端的响应时间。
建立缓存过程分析
我们从java最常用的方案开始——一个简单的HashMap。
public interface Computable<A, V> {
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computable<String, BigInteger> {
@Override
public BigInteger compute(String arg) throws InterruptedException {
// after deep thought...
return new BigInteger(arg);
}
}
Computable<A, V>接口描述了一个功能,输入类型是A,输出结果的类型是V。ExpensiveFunction实现了Computable。需要花比较长的时间来计算结果。所以我们计划把计算过的值都放进一个HashMap中,这样下一次有同一个A值进来时,直接获取A的计算结果。
2.1 Synchronized版
public class Memoizer1<A, V> implements Computable<A, V> {
private final Map<A, V> cache = new HashMap<A, V>();
private final Computable<A, V> c;
public Memoizer1(Computable<A, V> c) {
this.c = c;
}
@Override
public synchronized V compute(A arg) throws InterruptedException {
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
}
return result;
}
}
Memoizer1实现了第一个版本,HashMap不是线程安全的,所以使用synchronzied关键字来保证线程安全,如果cache变量中有计算结果,直接从cache取,不需要再次计算,省下许多时间。但使用synchronzied使得一次只有一个线程能够执行compute。如果一个线程正在计算结果,那其他调用compute的线程可能被阻塞很长时间,造成性能下降,这不是我们希望通过缓存得到的性能优化结果。
2.2 ConcurrentHashMap版
public class Memoizer2<A, V> implements Computable<A, V> {
private final Map<A, V> cache = new ConcurrentHashMap<>();
private final Computable<A, V> c;
public Memoizer2(Computable<A, V> c) {
this.c = c;
}
@Override
public V compute(A arg) throws InterruptedException {
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
}
return result;
}
}
Memoizer2用ConcurrentHashMap取代HashMap,改进了Memoizer1中那种糟糕的并发行为。因为ConcurrentHashMap是线程安全的,所以不需要使用Synchronized关键字,而是使用内部hash桶的分段锁机制。
Memoizer2与Memoizer1相比,毫无疑问具有了更好的并发性:多线程可以真正并发访问了。但是作为高速缓存仍然存在缺陷:当两个线程同时调用compute时,如果是计算同一个值,此时compute是需要很大的开销的,在一个线程还在计算中时,其它线程不知道,所以可能会重复计算。而我们希望的是:如果A线程正在计算arg,那B就不要重复计算arg,等A计算好,直接取arg对应的V就好了。
2.3 ConcurrentHashMap + FutureTask版
public class Memoizer3<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A, V> c;
public Memoizer3(Computable<A, V> c) {
this.c = c;
}
@Override
public V compute(A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = () -> {
return c.compute(arg);
};
FutureTask<V> ft = new FutureTask<>(eval);
f = ft;
cache.put(arg, ft);
ft.run(); // 调用 c.compute发生在这里
}
try {
return f.get();
} catch (ExecutionException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
Memoizer3为缓存的值重新定义可存储Map,用ConcurrentHashMap<A, Future>取代ConcurrentHashMap<A,V>。Memoizer3首先检查一个相应的计算是否开始,如果不是,就创建一个FutureTash,并把它注册到map中,并开始计算,如果是,那么它就会等待正在计算的结果。
Memoizer3的实现近乎是完美的:它展示了非常好的并发性,能很快返回已经计算过的结果,如果新到的线程请求的是其它线程正在计算的结果,它也会耐心的等待。
Memoizer3只有一个问题,就是仍然存在这种可能性:2个线程同时计算arg,此时由于compute中的if代码块是非原子性的复合操作,2个线程会同时进入到if代码块中,依旧会同时计算同一个arg。但ConcurrentHashMap中提供了一个原子化的putIfAbsent方法,可以消除Memoizer3的隐患。
2.4 最终版
public class Memoizer<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A, V> c;
public Memoizer(Computable<A, V> c) {
this.c = c;
}
@Override
public V compute(A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = () -> {
return c.compute(arg);
};
FutureTask<V> ft = new FutureTask<>(eval);
f = ft;
cache.putIfAbsent(arg, ft);
ft.run(); // 调用 c.compute发生在这里
}
try {
return f.get();
} catch (ExecutionException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
Memoizer可以说是缓存的完美实现了:支持高并发,同一个参数计算也不会重复执行(多亏于ConcurrentHashMap的putIfAbsent原子化操作)。最终调用者通过调用Future.get(arg)方法获取计算结果。
建立高速缓存机制-java版的更多相关文章
- 2019春招面试高频题(Java版),持续更新(答案来自互联网)
第一模块--并发与多线程 Java多线程方法: 实现Runnable接口, 继承thread类, 使用线程池 操作系统层面的进程与线程(对JAVA多线程和高并发有了解吗?) 计算机资源=存储资源+计算 ...
- 【java项目实践】具体解释Ajax工作原理以及实现异步验证username是否存在+源代码下载(java版)
一年前,从不知道Ajax是什么,伴随着不断的积累,到如今常常使用,逐渐有了深入的认识. 今天,假设想开发一个更加人性化,友好,无刷新,交互性更强的网页,那您的目标一定是Ajax. 介绍 在具体讨论Aj ...
- AKKA文档2.1(java版)——什么是AKKA?
可扩展的实时事务处理 我们相信编写并发.容错.可扩展的应用相当的困难.盖因大多数时候我们一直在使用错误的工具和错误的抽象等级.AKKA就是为了改变这一切的.我们利用角色模型提升了抽象等级,并且提供了一 ...
- JCEF3——谷歌浏览器内核Java版实现(一):使用jawt获取窗体句柄
前言 最近一段时间研究谷歌浏览器内核.谷歌浏览器内核一直开源,并维护更新,它的开源项目中内核更新速度和Chrome浏览器版本更新进度一样!而且它不同于WebKit(值得一题的是谷歌浏览器已不使用Web ...
- 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息
第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...
- 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...
- 第五篇 :微信公众平台开发实战Java版之如何获取公众号的access_token以及缓存access_token
一.access_token简介 为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台 开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等, 开 ...
- Oracle Berkeley DB Java 版
Oracle Berkeley DB Java 版是一个开源的.可嵌入的事务存储引擎,是完全用 Java 编写的.它充分利用 Java 环境来简化开发和部署.Oracle Berkeley DB Ja ...
- pureMVC java版搭建流程
转自:http://blog.csdn.net/sutaizi/article/details/6588004 pureMVC 是一个轻量级的框架 它在 flex中非常流行(和cairngorm差不多 ...
随机推荐
- 如何实现批量上传----------Java解析excel
一.引子 在web平台开发中仅经常会遇到一下需要批量的问题,通常得做法是使用excel上传,下面主要介绍一下在实际开发中到的实例. 二.准备工作 1.需要导入的jar包(主要用到poi包) (1)po ...
- HashMap三百问
文章目录: 一.JDK1.7之HashMap 二.JDK1.8之HashMap 三.Hashtable JDK1.7之HashMap 1. 定义 HashMap实现了Map接口,继承AbstractM ...
- Leetcode(106)-从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树. 注意:你可以假设树中没有重复的元素. 例如,给出 中序遍历 inorder = [9,3,15,20,7] 后序遍历 postorder = [9,15,7 ...
- JVM升华篇
01 Garbage Collect(垃圾回收) 1.1 如何确定一个对象是垃圾? 要想进行垃圾回收,得先知道什么样的对象是垃圾. 1.1.1 引用计数法 对于某个对象而言,只要应用程序中持有该对象的 ...
- POJ 2923 Relocation(状压DP)题解
题意:有2辆车运货,每次同时出发,n(<10),各自装货容量c1 c2,问最少运几次运完. 思路:n比较小,打表打出所有能运的组合方式,用背包求出是否能一次运走.然后状压DP运的顺序. 代码: ...
- TensorFlow+restore读取模型
# 注意和前一或二篇Lenet训练并验证的文章从`y_conv = tf.nn.softmax(fc2)`起的不同 # 部分函数请参照前后2篇文章 import tensorflow as tf im ...
- DLL & Dynamic-link library
DLL & Dynamic-link library 动态链接库 .dll 动态链接库(英语:Dynamic-link library,缩写为 DLL)是微软公司在微软视窗操作系统中实现共享函 ...
- 微信公众号代码高亮美化工具 All In One
微信公众号代码高亮美化工具 All In One markdown 美化 mdnice 编辑器 https://www.mdnice.com/ https://github.com/mdnice ma ...
- SVG 2 & SVG & getPointAtLength & getPathSegAtLength
SVG 2 & SVG & getPointAtLength & getPathSegAtLength getPointAtLength SVG 1.x https://dev ...
- js navigator.wakeLock 保持屏幕唤醒状态
let lock; btn.addEventListener("click", async () => { try { if (lock) { lock.release(); ...