本程序使用ConcurrentHashMap,FutureTask构建高效的缓存。
Computable代表高开销的计算任务,接受A类型的入参,返回V类型的结果
interface Computable<A,V>{//计算组件
public V compute(A arg);//高开销的计算
}
采用装饰者模式对Computable组件进行装饰,增强computable方法,使之能对结果进行缓存。
public class Memoizer<A,V> implements Computable<A,V>{
//map不直接存放计算的结果,而是存放计算任务Future
private final ConcurrentMap<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(final A arg){
while(true){
Future<V> f = cache.get(arg);//返回缓存中的计算任务.
if(f==null){//(1)若果f为Null,说明在此之前没有线程执行过此任务.若f不为null,说明其他线程已经执行过该计算任务,或者正在执行该计算任务,则本线程可调用Feture.get()返回结果
Callable<V> task =new Callable<V>(){
@Override
public V call() throws Exception {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(task);//创建任务
f = cache.putIfAbsent(arg,ft);//向缓存中存放该任务
if(f==null){(2)//若f==null,说明本线程第一个存放该计算任务.否则,说明其他线程已经存放过该任务且正在执行计算任务,则本线程直接调用f.get()获取结果。
f= ft;
ft.run();//执行计算任务并得到计算结果
}
}
try {
return f.get();
} catch (InterruptedException | ExecutionException e) {
cache.remove(arg);
e.printStackTrace();
}
return null;
}
}
}
该程序需注意一下几点:
1.底层采用ConcurrentHashMap保证线程安全性。同时要注意程序中两处使用了if(f==null)的判断。第二处判断是为了处理这样一种情况:两个线程处理同一个计算任务(参数arg 相同),并且都通过第一处的if判断,各自new出了相同任务,都执行了 cache.putIfAbsent(arg,ft)。在第一个执行cache.putIfAbsent(arg,ft)操作的线程中,函数的返回值f 为Null,然后执行 ft.run() 即执行计算任务。此时第二个线程中f 的值不为null,不执行ft.run() ,执行f.get() 得到结果。
2.在cache中保存的并不是类型为V的计算结果(计算过程比较耗时)而是任务FutureTask。这减少了线程同时通过第一个if(f==null)的几率。