锁定老帖子 主题:缓存同步策略重构
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-11-29
Java并发实践上有一个案例,和这里的缓存有几分相似,
有一个很耗时的运算需要缓存其计算结果,采用备忘录模式实现,如: 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; } } 这里备忘录的整个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; } } 将线程安全性委给cache,注:这个方案的cache用了ConcurrentHashMap,它是线程安全的。相当于huangyh提出的全部不同步。 备忘录缓存方案三: 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()); } } } 他给出的第三种方案和leadyu的差不多,使用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()); } } } } |
|
返回顶楼 | |