几乎每一个服务器应用程序都会使用某种形式的高速魂村。复用已有的计算结果可以缩短等待时间,提高吞吐量,代价是占用更多的内存。
在下面例子中Computerable
public interface Computable<A,V>{
V computer(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computable<String,BigInteger>{
public BigInteger compute(String arg){
//after
return new BigInteger(arg);
}
}
public Memoizer1<A,V> implements Computable<A,V>{
@GuardedBy("this")
private final Map<A,V> cache = new HashMap<A,V>();
private final Computable<A,V> c;
public Memoizer1(Computable<A,V> c){
this.c = c;
}
public synchronized 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代替HashMap改进并发行为,因为ConcurrentHashMap是线程安全的。
然而这样的做法还是有问题,当一个线程启动了一个开销很大的计算,其他线程不知道这个这个计算正在进行中,可能又会重复这个计算
但是已经有一个类可以做这些事:FutuerTask.它代表了一个计算过程,可能结束,也可能在进行中。FutureTask.get只要结果可用,就会立即返回结果。否则一直阻塞到计算出结果。
所以可以改进为
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(final 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);
f = ft;
cache.put(arg, ft);
ft.run(); // call to c.compute happens here
}
try {
return f.get();
} catch (ExecutionException e) {
cache.remove(arg);
}
return null;
}
}
但是这样还存在一个漏洞–俩个线程可能同时计算相同的值。这是因为复合操作(缺少即加入)运行在底层map中,不能通过加锁来使它原子化。这可以通过ConcurrentHashMap中的原子化方法putIfAbsent解决。
缓存一个Future而不是同一个值会带来缓存污染的可能性。为了避免Memoizer如果发现计算被取消,就把Future从缓存中移除,如果发现RuntimeException也会移除。这些可以通过Future的自雷完成。他会为每一个结果关联一个过期时间,并周期性的扫描缓存中的过期访问。
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();
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw LaunderThrowable.launderThrowable(e.getCause());
}
}
}
}