10

Guava キャッシュを使用しているときに、よくわからない結果が得られます。

非同期で更新したい単一のキーキャッシュを実装しています。

毎秒キャッシュにアクセスし、refreshAfterWrite を 20 秒に設定しました。私のロード/リロード機能には 5 秒かかります。

load/reload メソッドの開始時に現在の時刻を出力すると、次のような結果が期待されます。

load 呼び出しは 00:00:00 に開始されました reload 呼び出し
は 00:00:25 に開始されました
reload 呼び出しは 00:00:50 に開始されました

したがって、ロードには 5 秒かかり、次の書き込みはその後 20 秒 (5+20=25) トリガーされます。その書き込みは、その後 50 秒 (25 + 5 + 20 = 50) 秒後に発生します。

代わりに私は得る:

load 呼び出しは 00:00:00 に開始されました reload 呼び出し
は 00:00:25 に開始されました
reload 呼び出しは 00:00:30 に開始されました

これは、最初のリロードの処理が完了した直後に 2 回目のリロードが発生したことを示しています。

未来が処理された後に書き込みが発生すると思ったので、次のリロードはその後 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その後の最初のものであるとしましょう。

  • エグゼキュータでタスクscheduleRefreshを開始するGet A calls 。refreshエントリ値参照は に置き換えられLoadingValueReferenceloadAsyncリロードの完了を待機するリスナーが追加されます。
  • Get A リロードのフォークされたタスクが完了し、ロックを取得します。
  • B コールを取得しますscheduleRefresh。アクセス時間はまだ更新されていないので、進み、 に入りinsertLoadingValueReferenceます。
  • StrongValueReferenceGet A リロードのフォークされたタスクは、書き込み時間を更新し、ロードが完了するため、値参照を に置き換えます。ロックが解除されます。
  • Get B は、値がまだロード中でないと判断したため、新しいリロードの開始に進みます。

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

于 2012-11-23T01:26:13.753 に答える