上篇博客通过HashMap的方式写了一个缓存的demo,这篇博客来改进一下这个Demo。
用并发容器类ConcurrentHashMap来代替HashMap来改进上面的例子,因为ConcurrentHashMap是线程安全的,所以在访问底层Map时候就不需要进行同步了,因为避免了上篇博客中的阻塞问题。
代码如下:
public interface Computable<A,V>{
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computalbe<String ,BigInteger>{
public BigInteger compute(String arg){
return new BigInteger(arg);
}
}
public class Memoizer2<A,V> implements Computable<A,V>{
private final Map<A,V> cache=new ConcurrentHashMap<A,V>();
private final Computable<A,V> c;
public Memoizer2(Computable<A,V>,c){
this.c=c;
}
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;
}
}
因为ConcurrentHashMap的线程安全性,所以Memorizer2比1要有更好的并发行为,但是其实这里面也还是有不足之处,当两个线程同时调用compute时候也是存在一个漏洞,可能会导致计算得到相同的值。
问题出现在与某个线程执行很长时间,其他线程并不知道这个线程正在执行,那么就可能会重复去计算。那么如何来解决这样的一个问题呢?
这时候就要用到FutureTask类了,FutureTask表示一个计算的过程,这个过程可能已经计算出结果,当然也可能正在执行。如果有结果那么FutureTask.get方法会立即返回结果,否则会一直阻塞,直到计算出结果再将其返回。
具体程序清单:
public interface Computable<A,V>{
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computalbe<String ,BigInteger>{
public BigInteger compute(String arg){
return new BigInteger(arg);
}
}
public class Memoizer3<A,V> implements Computable<A,V>{
private final Map<A,Future<V>> cache=new ConcurrentHashMap<A,Future<V>>();
private final Computable<A,V> c;
public Memoizer3(Computable<A,V>,c){
this.c=c;
}
public V compute(A arg) throws InterruptedException{
Future<V> f=cache.get(arg);
if(f==null){
Callable<V> eval=new Callable<V>(){
public V call() throws InterruptedException{
return c.compute(arg);
}
};
FutureTask<V> ft=new FutureTask<V>(eval);
cache.put(arg,ft);
ft.run();
}
try{
return f.get();
}catch(ExecutionException e){
}
}
}
上面的程序看起来应该是完美的了,但是其实还是有一些瑕疵,下篇博客再来完善。