缓存,就是把计算结果放入内存中,下次使用的时候,直接从缓存中获取,不用重新进行耗时的计算,以提高效率。
那我们如何创建缓存呢?首先我们编写以下代码:
public interface Computable<A, V> {
/**
* 耗时的计算逻辑
* @param arg
* @return
* @throws InterruptedException
*/
V compute(A arg) throws InterruptedException;
}
Computable接口表示耗时的计算逻辑,传入参数A,计算得到结果V;我们需要把结果V放入缓存中,以便下次传入A是立刻得到V结果。
public class Memoizer1<A, V> implements Computable<A, V> {
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;
}
}
类Memoizer1实现了这样的缓存,因HashMap是线程不安全的,在执行方法compute时加锁,保证只有一个线程进入此方法。这样是弱并发的,导致用缓存还不如每次都计算来的快;显然是不合理的。
如此我们就有了类Memoizer2,用线程安全的ConcurrentHashMap取代HashMap,改进Memoizer1糟糕的并发行为。
public class Memorizer2<A, V> implements Computable<A, V>{
private final Map<A, V> cache = new ConcurrentHashMap<A, V>();
private final Computable<A, V> c;
public Memorizer2(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;
}
}
这样就可以了吗? 让我们来假设这种情况, 当一个线程正在计算compute方法的时候,又有另外线程来获得计算结果,但是它们并不知道这个计算正在进行中,所有可能又会重复计算。
so,我们希望无论用什么方法,能够表现出“线程X正在计算”,这样另外的线程到达查找结果,能够判断出已经有线程在计算中, 等待计算结果,然后拿走结果。
谁能达到这样的效果呢? FutureTask。 对,FutureTask.get 方法会一直阻塞,等待结果计算出来,并返回。这样我们的Memorizr3就出来了
public class Memorizer3<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 Memorizer3(Computable<A, V> c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if(f == null){
FutureTask<V> ft = new FutureTask<V>(new Callable<V>() {
public V call() throws Exception {
return c.compute(arg);
}
});
f = ft;
cache.put(arg, ft);
ft.run();//调用c.compute在这里
}
try {
return f.get();
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
}
这样就完美了吗?
还存在唯一的一个缺陷!