使用异步加载加载缓存

时间:2013-07-03 19:52:53

标签: java caching guava

在guava中,使用LoadingCache时会同步调用CacheLoader。但是,我的load()操作可能需要很长时间(约1秒),我想采取默认操作,以防它花费太长时间(> 200毫秒)并异步加载值。

有没有办法实现这个目标?或者你可以推荐其他方法吗?

3 个答案:

答案 0 :(得分:5)

您可以按照正常方式执行此操作:提交任务以将缓存值设为ExecutorService,在get(200, MILLISECONDS)上调用Future,如果超时则执行其他操作。

示例:

final LoadingCache<Key, Result> cache = ...
final Key key = ...
ExecutorService executor = ...

Future<Result> future = executor.submit(new Callable<Result>() {
  @Override public Result call() throws Exception {
    return cache.get(key);
  }
});

try {
  Result result = future.get(200, TimeUnit.MILLISECONDS);
  // got the result; do stuff
} catch (TimeoutException timeout) {
  // timed out; take default action
}

答案 1 :(得分:4)

Caffeine library是Java 8对Guava缓存的重写,它允许异步自动将条目加载到缓存中,并返回CompletableFutures。它是由直接参与创建Guava缓存的人员编写的,并使用了Guava启发的API(包括用于Guava接口的适配器)。

根据Guava邮件列表上的帖子,它基于Guava缓存库的原始建议,并且包括原本打算用于Guava本身的更改,但由于各种原因而未包括在内(包括Guava需要兼容)使用旧版Java)。

实际上,某些项目现在认为不赞成使用Guava缓存,而是改用Caffeine,e.g. Spring has switched to Caffeine,作者指出“ Caffeine是ConcurrentLinkedHashMap和Guava缓存的Java 8继承者。项目应首选Caffeine,并在需要时进行迁移JDK8或更高版本。”

答案 2 :(得分:1)

您可以使用getIfPresent命令检查值是否为null。 如果值为null,那么您可以提交一个异步加载值的任务并继续使用您的流程。

示例:

Map<String, String> map = cache.getIfPresent(key);
if(map == null){
 executorService.submit(new Callable<Map<String, String>>() {
    @Override
    public Map<String, String> call() throws Exception {
        return cache.get(key);
    }   
 }); 
}
else{
    continue with the flow...
}

如果要在仍在读取旧值时刷新缓存中的值,还应使用refreshAfterWrite功能并实现重装方法。 通过这种方式,缓存将始终更新,读取值的主线程不会受到影响。

示例:

cache = CacheBuilder.newBuilder()
    .refreshAfterWrite(30, TimeUnit.SECONDS)
    .build( 
        new CacheLoader<String, Map<String,String>>() {

            public Map<String, String> load(String key) throws Exception {
                map = hardWork(key)//get map from from DB -- expensive time commend
                return map;
            }

            @Override
                public ListenableFuture<Map<String, String>> reload(final String key, Map<String, String> oldValue) throws Exception {
                    // we need to load new values asynchronously, so that calls to read values from the cache don't block
                    ListenableFuture<Map<String, String>> listenableFuture = executorService.submit(new Callable<Map<String, String>>() {

                        @Override
                        public Map<String, String> call() throws Exception {
                            //Async reload event
                            return load(key);
                        }
                    }); 

                    return listenableFuture;
                }
    });