在这篇博客中,主要把之前看的书的内容记录一下,个人感觉还是可以的,原题是这样的:开发一个高效的缓存。这里指的是单机.

首先我来看当前的一个版本

 public interface Computable<T, R> {
R compute(T input) throws InterruptedException;
}
 public class Memoizer1<T,R> implements  Computable<T,R>{

     private  final Map<T,R> cache = new HashMap<>();

     private  final Computable<T,R> computable;

     public Memoizer1(Computable<T, R> computable) {
this.computable = computable;
} public synchronized R compute(T input) throws InterruptedException {
R result= cache.get(input);
if(result ==null){
result = computable.compute(input);
cache.put(input,result);
}
return result;
}
}

在该版本中利用HashMap来保存之前计算的结果,compute方法首先检查缓存中是否有结果,没有则计算,把其结果放入缓存并且返回。大家都知道HashMap不是线程安全的,因此要确保多个线程同时访问的时,Memoizer1采用把对整个方法compute进行同步,这样的结果导致调用该方法被串行化,如果compute的执行时间比较长,那么后面的线程需要等待更长的时间,结果可能比不用缓存更加糟糕。

接着我们对该版本进行进一步的优化,把HashMap改为ConcurrentHashMap,因为ConcurrentHashMao是线程安全的,因此在访问底层的Map的时候不需要进行同步。因此避免了在对compute方法进行同步带来的串行性。

 public class Memoizer2<T,R> implements  Computable<T,R>{

     private  final Map<T,R> cache = new ConcurrentHashMap<>();

     private  final Computable<T,R> computable;

     public Memoizer2(Computable<T, R> computable) {
this.computable = computable;
} public R compute(T input) throws InterruptedException {
R result= cache.get(input);
if(result ==null){
result = computable.compute(input);
cache.put(input,result);
}
return result;
}
}

在该版本也存在一些不足,当两个线程同时调用compute时存在一个漏洞,可能会导致计算相同的值。缓存的目的是避免相同的数据被多次计算。我们知道FutureTask 表示一个计算过程,这个过程可能已经完成,也可能正在进行。如果FutureTask结果可用,调用FutureTask.get()将立即得到结果,否则它会一直阻塞,知道计算结果出来再返回。

 public class Memoizer3<T,R> implements  Computable<T,R>{

     private  final Map<T,Future> cache = new ConcurrentHashMap<>();

     private  final Computable<T,R> computable;

     public Memoizer3(Computable<T, R> computable) {
this.computable = computable;
} public R compute(final T input) throws InterruptedException {
Future<R> future = cache.get(input);
if (future==null){
Callable<R> callable = new Callable<R>() {
@Override
public R call() throws Exception {
return computable.compute(input);
}
};
FutureTask futureTask = new FutureTask(callable);
future=futureTask;
cache.put(input,future);
futureTask.run();
}
try{
return future.get();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}

在第三个版本Memoizer3的实现几乎是完美的,它表现出非常好的并发性,如果结果已经计算出来则直接返回,如果其他线程正在计算该结果,那么新到的线程将一直等待这个结果被计算出来。它只有一个缺陷,即仍然存在两个线程计算出相同值的漏洞。

但是这个概率将远远小于第二个版本。由于compute方法中的if代码仍然是非原子的“先检查后执行”操作。因此两个线程仍然有可能在同一时间内调用compute来计算相同的值。在该版本中存在该问题的原因是,复合操作在底层Map对象上执行,而这个对象无法通过加锁来确保原子性。那么接着把Map.put()改为Map.putIfAbsent()即可。

 public class Memoizer4<T,R> implements  Computable<T,R>{

     private  final ConcurrentMap<T,Future> cache = new ConcurrentHashMap<>();

     private  final Computable<T,R> computable;

     public Memoizer4(Computable<T, R> computable) {
this.computable = computable;
} public R compute(final T input) throws InterruptedException {
while (true){
Future<R> future = cache.get(input);
if (future==null){
Callable<R> callable = new Callable<R>() {
@Override
public R call() throws InterruptedException {
return computable.compute(input);
}
};
FutureTask futureTask = new FutureTask(callable);
future= cache.putIfAbsent(input, futureTask);//如果原先不存在,返回null
if(future==null){
future=futureTask;
futureTask.run();
}
}
try{
return future.get();
} catch (CancellationException e) {
cache.remove(input,future);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}

在该版本中应该完美了解决了原先提出的问题。但是还是存在如下问题:没有解决缓存预期的问题,没有解决缓存清理问题。

Java并发,看到了,就记录下呗的更多相关文章

  1. Java并发编程的艺术 记录(一)

    模拟死锁 package com.gjjun.concurrent; /** * 模拟死锁,来源于<Java并发编程的艺术> * @Author gjjun * @Create 2018/ ...

  2. Java并发编程的艺术 记录(三)

    Java内存模型 并发编程的两个关键问题: 1.线程之间如何通讯. 2.线程间如何同步. 两种方式:共享内存和消息传递. Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通 ...

  3. Java并发编程的艺术 记录(二)

    volatile的应用 volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量.Java语言提供了volatil ...

  4. Java并发编程的艺术 记录(四)

    Java线程的状态: new :初始状态,但是还没调用start方法. runnable:运行状态. blocked:阻塞状态. waiting:等待状态,表示当前线程需要等待其他线程作出一些特定动作 ...

  5. Java并发机制(8)--concurrent包下辅助类的使用

    Java并发编程:concurrent包下辅助类的使用 整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920397.html 1.CountDown ...

  6. 【Java并发编程】并发编程大合集-值得收藏

    http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...

  7. 【Java并发编程】并发编程大合集

    转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅 ...

  8. Java 并发编程(二):如何保证共享变量的原子性?

    线程安全性是我们在进行 Java 并发编程的时候必须要先考虑清楚的一个问题.这个类在单线程环境下是没有问题的,那么我们就能确保它在多线程并发的情况下表现出正确的行为吗? 我这个人,在没有副业之前,一心 ...

  9. java并发编程之美-阅读记录11

    java并发编程实践 11.1ArrayBlockingQueue的使用 有关logback异步日志打印中的ArrayBlockingQueue的使用 1.异步日志打印模型概述 在高并发.高流量并且响 ...

  10. java并发编程之美-阅读记录1

    1.1什么是线程? 在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体.(线程是不会独立存在的) 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的 ...

随机推荐

  1. 彻底理解容器类(2)------- AbstractCollection深入了解

    h2 { background-color: Skyblue } AbstractCollection认识 AbstractCollection是Collection接口的抽象实现.实现了一部分Col ...

  2. bootstrap使用模板

    Bootstrap下载地址: - https://github.com/twbs/bootstrap/releases/download/v3.3.6/bootstrap-3.3.6-dist.zip ...

  3. 《Django By Example》第十二章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:第十二章,全书最后一章,终于到这章 ...

  4. JS中new的运行方式

    ---恢复内容开始--- 在JS中,有两个基础原型,分别是Function.prototype和Object.prototype.这两个原型组成了JS中的所有实例他们的关系是 Function.pro ...

  5. 安装Postgresql

    p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-bottom: .0001pt; line-height: 150%; fon ...

  6. AVL树的旋转操作详解

    [0]README 0.0) 本文部分idea 转自:http://blog.csdn.net/collonn/article/details/20128205 0.1) 本文仅针对性地分析AVL树的 ...

  7. 以正确的姿势实现一棵JavaScript菜单树

    菜单树是常见的前端特效, 一般长下面这样 还有各种形态的变种, 有长这样的 也有长这样的 尽管这些菜单的相貌都不尽相同, 在功能实现的本质上却都是相同的.实现程序的大致流程如下 读取服务器端的菜单数据 ...

  8. Sphinx安装流程及配合PHP使用经验

    1.什么是Sphinx Sphinx是俄罗斯人Andrew Aksyonoff开发的高性能全文搜索软件包,在GPL与商业协议双许可协议下发行. 全文检索式指以文档的全部文本信息作为检索对象的一种信息检 ...

  9. 和我一步步部署 kubernetes 集群

    和我一步步部署 kubernetes 集群 本系列文档介绍使用二进制部署最新 kubernetes v1.6.1 集群的所有步骤,而不是使用 kubeadm 等自动化方式来部署集群: 在部署的过程中, ...

  10. 编译MangosZero

    最近研究了一下魔兽世界模拟器MangosZero,花了两天时间终于编译成功!现在把编译的过程做个完整的记录,以便让想要学习编译的同学们少走弯路! 服务器端运行界面: 客户端运行界面: 一:下载源程序 ...