建立高速缓存机制-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差不多 ...
随机推荐
- 基于OpenCV全景拼接(Python)SIFT/SURF
一.实验内容: 利用sift算法,实现全景拼接算法,将给定的两幅图片拼接为一幅. 二.实验环境: 主机配置: CPU :intel core i5-7300 2.50GHZ RAM :8.0GB 运行 ...
- USB2.0协议学习笔记---USB工作过程(类的方法)
前面学习了那么多的概念,这里需要记住一点分层概念即设备 ---> 配置 ---> 接口 ---> 端点,这种分层的概念结构 . 也可以理解为端点构成接口,接口组成配置,配置组成设备. ...
- GitHub Secrets All In One
GitHub Secrets All In One https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypte ...
- expo-cli & React Native
expo-cli https://reactnative.dev/docs/environment-setup You will only need a recent version of Node. ...
- skills share & free videos
skills share & free videos 技术分享 & 免费视频 https://www.infoq.cn/video/list WebAssembly https://w ...
- 用Qt写了个将视频设置为壁纸的软件
软件功能很简单,使用时占用的资源和播放的视频有关: 依赖于FFplay,Github源码 效果图:
- dart2native 使用Dart 在macOS,Windows或Linux上创建命令行工具
下载dart2.6以上 >dart2native --help 编写源文件 // bin\main.dart main(List<String> args) { print('hel ...
- NGK算力市场,不止有动静态收益还有SPC空投!
随着数字货币交易的火热,云算力挖矿也悄然崛起.越来越多的用户开启云算力挖矿,以获取更多的收益.相较于传统的矿机挖矿,用户通过购买算力进行云挖矿,节省了购买矿机以及维护的成本.另一方面,也降低了安全风险 ...
- 10月上线的NGK global有怎样的发展前景?
随着NGK global 10月份的上线时间将近,社区中也开始纷纷讨论.预测起NGK global上线后的表现,对此小编也有一些自己的理解,就此分享给大家. 在基于实体生态的赋能下,NGK globa ...
- PAUL ADAMS ARCHITECT:日本楼市仍保持稳定
日本国土交通省从2008年11月开始,到2020年10月,连续追踪日本的公寓房价和日经指数两个数值的变动关联性,结果显示相关系数是0.935,也就是说在此期间,日本楼市和股市有着非常强的正相关. 保罗 ...