10

我在使用 Guava Caches 时得到了我不太了解的结果。

我正在实现一个我想异步刷新的单键缓存。

我每秒都会访问缓存,并将 refreshAfterWrite 设置为 20 秒。我的加载/重新加载功能需要 5 秒。

如果我在加载/重新加载方法开始时打印出当前时间 - 我希望得到如下结果:

加载呼叫开始于 00:00:00
重新加载呼叫开始于 00:00:25
重新加载呼叫开始于 00:00:50

因此加载需要 5 秒,下一次写入将在 20 秒后触发(5+20=25)。该写入将在 50 秒 (25 + 5 + 20 = 50) 秒后发生..等等

相反,我得到:

加载呼叫开始于 00:00:00
重新加载呼叫开始于 00:00:25
重新加载呼叫开始于 00:00:30

这表明第二次重新加载直接发生在第一次重新加载完成处理之后。

我认为写入将在处理未来之后发生,因此下一次重新加载将安排在 20 秒之后?

我是否发现了一个错误,或者我对 refreshAfterWrite 的工作原理有根本的误解?

示例代码如下:

private static SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss");

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        final ExecutorService executor = Executors.newFixedThreadPool(3);

        final LoadingCache<String, Long> cache = CacheBuilder.newBuilder().maximumSize(1) //
                .refreshAfterWrite(20, TimeUnit.SECONDS)//
                .build(new CacheLoader<String, Long>() {//
                    public Long load(String key) {
                        return getLongRunningProcess("load", key);
                    }

                    public ListenableFuture<Long> reload(final String key, Long prevGraph) {
                        ListenableFutureTask<Long> task = ListenableFutureTask.create(new Callable<Long>() {
                            public Long call() {
                                return getLongRunningProcess("reload", key);
                            }
                        });
                        executor.execute(task);
                        return task;
                    }
                });

        while (true) {
            Thread.sleep(1000L);
            cache.get(CACHE_KEY);
        }
    }

    private static Long getLongRunningProcess(String callType, String key) {
        System.out.printf("%s call started at %s\n", callType, format.format(new Date()));
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return counter.getAndIncrement();
    }

}
4

1 回答 1

8

你找到了一个合法的错误。(我帮助维护common.cache。)

如果我正确地遵循事情,我相信事件链如下:

假设 get A 是第一个get导致刷新的,而 get B 是get之后的第一个。

  • 获取 A 调用scheduleRefresh,它在执行程序中启动refresh任务。条目值引用被替换为LoadingValueReference,并loadAsync添加了一个等待重新加载完成的侦听器。
  • Get A reload 的分叉任务完成并获取锁。
  • 接 B 电话scheduleRefresh。访问时间还没有更新,所以它继续,进入insertLoadingValueReference.
  • Get A 重新加载的分叉任务更新写入时间,并将值引用替换为 a StrongValueReference,因为加载已完成。锁被释放。
  • Get B 确定该值不在加载过程中,因此它继续启动新的重新加载。

(更新:提交https://code.google.com/p/guava-libraries/issues/detail?id=1211。)

于 2012-11-23T01:26:13.753 回答