为计算结果建立高效、可伸缩的高速缓存
复用已有的计算结果可以缩短等待时间,提高吞吐量,代价是占用更多的内存。
Memoizer利用ConcurrentMap中原子化的putIfAbsent方法,缓存一个Future而不是一个值,带来了缓存污染的可能性:如果一个计算被取消或者,失败,未来尝试对这个值进行计算都会表现为取消或者失败。为了避免这个结果,Memoizer如果发现计算被取消,就会把Future从缓存中移除;如果发现有RuntimeException,也会移除Future,这样新请求中的计算才有可能成功。Memoizer同样有缓存过期的问题,但是这些可以通过FutureTask的一个子类来完成,它会为每一个结果关联一个过期时间,并且周期性地扫描缓存中过期的访问。例子是把servlet因式分解的结果缓存起来。
public interface Computale<A,V> {
V compute(A arg) throws InterruptedException;
}
public class Memoizer<A,V> implements Computable<A,V> {
private final ConcurrentMap<A, Future<V>> cache =
new ConcurrentHashMap<A, Future<V>>();
private final Computable<A,V> c;
public Memoizer (Computable<A,V> c) { this.c = c;}
public V compute(final A arg) throws InterruptedException {
while (true) {
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);
f = cache.putIfAbsent(arg, ft);
if (f == null) {f = ft; ft.run();} //执行compute方法
}
try {
return f.get();
}catch (CancellationException e) {
cache.remove(arg,f);
}catch(ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
}
使用Memoizer为因式分解的servlet缓存结果
@ThreadSafe
public clas Factorizer implements Servlet {
private final Computable<BigInteger, BigInteger[]> c =
new Computable<BigInteger, BigInteger[]>() {
public BigInteger[] compute(BigInteger arg) {
return factor(arg);
}
};
private final Computable<BigInteger, BigInteger[]> cache =
new Memoizer<BigInteger, BigInteger[]>(c);
public void servie(ServletRequest req , ServletResponse resp) {
try{
BigInteger i = extractFromRequest(req);
encodeIntoResponse(resp, cache.compute(i));
}catch(InterruptedException e) {
encodeError(resp, "factorization interrupted");
}
}
}