有一个很耗时的运算需要缓存其计算结果,采用备忘录模式实现,如:
- public interface Computable<A, V> {
- V compute(A arg) throws InterruptedException;
- }
-
- public class ExpensiveFunction
- implements Computable<String, BigInteger> {
- public BigInteger compute(String arg) {
- // 假设这里非常耗时...
- return new BigInteger(arg);
- }
- }
public interface Computable<A, V> {
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction
implements Computable<String, BigInteger> {
public BigInteger compute(String arg) {
// 假设这里非常耗时...
return new BigInteger(arg);
}
}
备忘录缓存方案一:
- public class 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;
- }
- }
public class 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;
}
}
这里备忘录的整个compute过程被同步,相当于我最原始的低效方案,
备忘录缓存方案二:
- 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;
- }
- }
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;
}
}
将线程安全性委给cache,注:这个方案的cache用了ConcurrentHashMap,它是线程安全的。
备忘录缓存方案三:
- 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) {
- throw launderThrowable(e.getCause());
- }
- }
- }
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) {
throw launderThrowable(e.getCause());
}
}
}
使用Future(相当上一帖的Entry)封装,因为这里资源获取过程不固定(通用方案),所以使用了Callable进行回调资源获取过程(求值),
这个方案的 if (f == null) 不安全,特殊性可能会进行多次运算,下面的方案四解决这个问题。
备忘录缓存方案四(最终方案):
- 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(e.getCause());
- }
- }
- }
- }