构建高效且可伸缩的结果缓存
1,缓存在服务器应用程序中是一个非常重要的组件。
2,以下讲解一个高效且可伸缩的缓存示例
1,缓存在服务器应用程序中是一个非常重要的组件。
2,以下讲解一个高效且可伸缩的缓存示例
代码如下
public class CacheSample<IN, OUT> {
/*
* 缓存类容器
* 1,选择线程安全的ConcurrentMap,它提供了并发读写的线程安全,以及“先检查再执行”这样的原子操作putIfAbsent
* 2,选择Future,避免直接存储计算结果值,导致高并发下多个线程同时计算同一个值的情况
*/
private final ConcurrentMap<IN, Future<OUT>> cache = new ConcurrentHashMap<IN, Future<OUT>>();
public OUT doSomething(final IN arg) throws InterruptedException {
while (true) {
Future<OUT> f = cache.get(arg);
if (f == null) {
// 如果没有IN对应的Future,则创建Future
Callable<OUT> eval = new Callable<OUT>() {
public OUT call() throws InterruptedException {
return doing(arg);
}
};
FutureTask<OUT> ft = new FutureTask<OUT>(eval);
// 通过putIfAbsent完成“先检查再添加”的原子操作
// putIfAbsent如果已存在则返回已存在的值,如果不存在则添加新建的Future并返回null
f = cache.putIfAbsent(arg, ft);
if (f == null) {
// 如果新添加进缓存,则启动计算
f = ft;
ft.run();
}
}
try {
// 不管f是新创建的还是通过缓存中获取的,如果Future还没计算结束就阻塞,如果计算结束就获取值
return f.get();
} catch (CancellationException e) {
// 当某个计算被取消或者失败,需要从缓存中移除该IN及对应的Future
// 否则,将持续性获取不到正确的值,但同时又无法再进行新的计算(缓存污染)
cache.remove(arg, f);
} catch (ExecutionException e) {
throw LaunderThrowable.launderThrowable(e.getCause());
}
}
}
private OUT doing(final IN arg){
// 非常耗时的处理过程................
return null;
}
}