- 复用已有的计算结果,缩短等待时间,提高吞吐量,代价是占用更多的内存
- 经典思路:使用一个map,每次计算先在map中查找,找不到的情况下进行计算,把计算结果保存到map中,以便下次计算相同值时直接从map中取得计算结果
/**
* 这里定义一个Computable接口,实际的计算类和使用的缓存的计算类都实现该接口
* 使用的时候可以方便的切换是否使用缓存
*
* @param <A>
* @param <V>
*/
public interface Computable<A, V> {
V computer(A arg) throws Exception;
}
import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* 实现了Computable接口的计算类,这里模拟了一个复杂耗时的计算
*
*/
class ExpensiveFunction implements Computable<String, BigInteger>{
@Override
public BigInteger computer(String arg) throws Exception {
//after deep thought
//...
return new BigInteger(arg);
}
}
/**
*使用了缓存的计算类
*
* @param <A>
* @param <V>
*/
public class Memoizer<A,V> implements Computable<A,V> {
//使用并发容器ConcurrentHashMap,增强并发性
//key是计算因子,计算结果使用进行异步调用的Future储存,Future代表了一个计算过程
//计算的时候,先在cache中找该计算过程,获得计算结果;如果cache中不存在该计算过程,则新建一个计算过程,进行计算。
//在新计算过程 开始计算--得到结果 之前,就把这个计算过程给缓存了,这时如果有多个计算请求arg(计算因子)都是相同的,
//则返回给他们这个计算过程,让其阻塞等待结果,最终实现了每个arg(计算因子)只计算一次。
private final ConcurrentHashMap<A,Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
private final Computable<A,V> c ;//实际进行计算的类
public Memoizer(Computable<A,V> c){
this.c=c; //把实际计算类的对象传进来
}
@Override
public V computer(final A arg) throws Exception {
while(true){
Future<V> f = cache.get(arg);//直接获取该计算过程
if(f==null){//如果计算过程不存在
//通过Callable接口new一个FutureTask(计算过程,实现了Future接口)
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return c.computer(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
//cache.putIfAbsent(arg, ft)
//方法:如果arg和ft在ConcurrentHashMap中已建立对应关系,则返回cache.get(arg);
//如果没有建立关系,则简历关系,并且返回null
f=cache.putIfAbsent(arg, ft);
if(f==null){//当缓存中不存在此计算过程
f=ft;//把new出来的FutureTask赋值给f,下面调用f.get()的时候得到的是当前new出来的FutureTask的计算结果
ft.run();//开启计算过程
}
}
try{
//如果计算过程已经存在了,则直接调用FutureTask的阻塞方法get()
//该方法会一直阻塞到计算出结果
return f.get();
}catch(CancellationException e){
cache.remove(arg);//如果计算被取消了,则把缓存中的计算过程移除
}catch(ExecutionException e){
//调用方法处理其他的异常,这里的处理方法在之前的文章Synchronizer...中有提到
throw launderThrowable(e.getCause());
}
}
}
//通过launderThrowable()方法处理其他情况的异常1.如果cause是一个Error,抛出;2.如果是一个RuntimeException,返回该异常;3.其他情况下抛出IllegalStateException
//暂时我也不清楚为什么要这么处理,把Exception return出来有什么用么?还是要回头看java基础啊~~
private Exception launderThrowable(Throwable cause) {
if(cause instanceof Error)
throw new Error(cause);
else if(cause instanceof RuntimeException)
return (Exception) cause;
else throw new IllegalStateException(cause);
}
}