Java并发,看到了,就记录下呗
在这篇博客中,主要把之前看的书的内容记录一下,个人感觉还是可以的,原题是这样的:开发一个高效的缓存。这里指的是单机.
首先我来看当前的一个版本
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并发,看到了,就记录下呗的更多相关文章
- Java并发编程的艺术 记录(一)
模拟死锁 package com.gjjun.concurrent; /** * 模拟死锁,来源于<Java并发编程的艺术> * @Author gjjun * @Create 2018/ ...
- Java并发编程的艺术 记录(三)
Java内存模型 并发编程的两个关键问题: 1.线程之间如何通讯. 2.线程间如何同步. 两种方式:共享内存和消息传递. Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通 ...
- Java并发编程的艺术 记录(二)
volatile的应用 volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量.Java语言提供了volatil ...
- Java并发编程的艺术 记录(四)
Java线程的状态: new :初始状态,但是还没调用start方法. runnable:运行状态. blocked:阻塞状态. waiting:等待状态,表示当前线程需要等待其他线程作出一些特定动作 ...
- Java并发机制(8)--concurrent包下辅助类的使用
Java并发编程:concurrent包下辅助类的使用 整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3920397.html 1.CountDown ...
- 【Java并发编程】并发编程大合集-值得收藏
http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...
- 【Java并发编程】并发编程大合集
转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅 ...
- Java 并发编程(二):如何保证共享变量的原子性?
线程安全性是我们在进行 Java 并发编程的时候必须要先考虑清楚的一个问题.这个类在单线程环境下是没有问题的,那么我们就能确保它在多线程并发的情况下表现出正确的行为吗? 我这个人,在没有副业之前,一心 ...
- java并发编程之美-阅读记录11
java并发编程实践 11.1ArrayBlockingQueue的使用 有关logback异步日志打印中的ArrayBlockingQueue的使用 1.异步日志打印模型概述 在高并发.高流量并且响 ...
- java并发编程之美-阅读记录1
1.1什么是线程? 在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体.(线程是不会独立存在的) 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的 ...
随机推荐
- 类string解析
原创作品,转载请注明来源:http://www.cnblogs.com/shrimp-can/p/5645248.html 在涉及字符串的时候,我们可以定义字符数组或指针,其实还有一个类,专门是为字符 ...
- 常见端口、端口查询及TCP状态
查看电脑端口的开放情况命令:cmd——netstat -a -n -a:显示所有连接和监听端口:-n:以数字形式显示地址和端口号 “本地地址”指本地IP地址及其正在使用的端口号,“外部地址”指连接某端 ...
- JavaScript 方法调用模式和函数调用模式
这两天在读<JavaScript语言精粹>关于第4章函数调用的几种模式琢磨了半天. 这里就说一下方法调用模式跟函数调用模式. 方法调用模式: 当一个函数被保存为对象的一个属性时,我们称它为 ...
- winow7安装django 1.9.1
1.下载django https://www.djangoproject.com/download/ 2.解压,并到该目录下 执行 python setup.py install 3.验证是否安装成功 ...
- 模块中为什么要加__name__ == "__main__"
写一个hello模块 #!/usr/sbin/env python #-*- coding:utf- -*- print "我是hello模块,我被执行了" 在另一个python程 ...
- c++学习笔记之继承篇
title: c++学习笔记之继承篇 date: 2017-03-26 16:36:33 tags: [c++,继承,public,virtual,private,protected] categor ...
- Intellij IDEA中文乱码解决
界面乱码 原因:IDEA默认设置界面的字体不支持中文 解决:修改为支持中文的字体,建议字体Microsoft Yahei UI.大小11,具体操作File -> Setting -> Ap ...
- TextRank:关键词提取算法中的PageRank
很久以前,我用过TFIDF做过行业关键词提取.TFIDF仅仅从词的统计信息出发,而没有充分考虑词之间的语义信息.现在本文将介绍一种考虑了相邻词的语义关系.基于图排序的关键词提取算法TextRank [ ...
- 创建keystone的catalog时提示:‘Internal Server Error (HTTP 500)’
在生成keystone的catalog时: [root@controller ~]# openstack service create --name keystone --description &q ...
- Appium 解决手势密码 java
if(driver.getPageSource().contains("绘制解锁图案")){//检测是否是有手势密码出现 List<AndroidElement> el ...