私はGuavaのLoadingCacheをプロジェクトに使用して、スレッド{safe、friendly}キャッシュの読み込みを処理していますが、これは非常にうまく機能します。ただし、制限があります。
キャッシュを定義する現在のコードは次のようになります。
cache = CacheBuilder.newBuilder().maximumSize(100L).build(new CacheLoader<K, V>()
{
// load() method implemented here
}
有効期限は指定しません。
問題は、キーの値に応じて、関連付けられている値の一部が期限切れになる場合と期限切れにならない場合があることです。また、CacheLoader
はこれを考慮していません。有効期限を指定すると、すべてのエントリに適用されます。
この問題にどのように取り組みますか?
別の選択肢は ExpiringMap で、これは変数エントリの有効期限をサポートします。
Map<String, String> map = ExpiringMap.builder().variableExpiration().build();
map.put("foo", "bar", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES);
map.put("baz", "pez", ExpirationPolicy.CREATED, 10, TimeUnit.MINUTES);
有効期限をエントリクラスに直接含め、キャッシュからフェッチした直後に有効期限が切れた場合は、手動でキャッシュから削除することをお勧めします。
MyItem item = cache.getIfPresent(key);
if (item != null && item.isExpired()) {
cache.invalidate(key);
item = cache.get(key);
// or use cache.put if you load it externally
}
別の方法として、要素ごとの有効期限ポリシーをサポートするEhCacheライブラリを確認することをお勧めします。
LoadingCache
は、一般的に使用される有効期限ポリシーをいくつか提供しますが、これらが必要なものに満たない場合は、独自にロールする必要があります。
DelayQueue を追加するだけです。キャッシュに何かを追加するときはいつでも、適切な有効期限を指定して、そのキューにDelayed
を追加してください。 Delayed
オブジェクトには、キーへの(弱い?)参照が必要です。
最後の要素は、このキューを定期的にポーリングして、何かが期限切れになり、削除する必要があるかどうかを確認する必要があることです。これを行うために必ずしもスレッドを追加する必要はありません。LoadingCache
にアクセスしているスレッドに便乗するだけで済みます。キャッシュにアクセスする直前。例:
private void drainCache() {
MyDelayed expired;
while ((expired = delayedQueue.poll()) != null) {
K key = expired.getReference();
if (key != null) { // this only in case if you hold the key in a weak reference
loadingCache.invalidate(key);
}
}
}
..
V lookup(K key) {
drainCache();
return loadingCache.getUnchecked(key);
}
エントリが大きく、メモリを節約する必要がある場合、良い解決策はないと思いますが、これらのハックが念頭にあります:
エントリを手動で削除するには、有効期限順に並べられたPriorityQueue
を使用します。期限切れのエントリが使用されないようにしたい場合は、これをhoazによるソリューションと組み合わせる必要があります。キューは、役に立たないエントリがメモリを占有するのを防ぐだけです。
「関連付けられている値の中には有効期限が切れているものとそうでないものがある」と書いたものです。これは、有効期限が切れるすべてのエントリで有効期限の遅延が同じであることを示しています。これにより、よりシンプルで高速なQueue
を使用できるようになります(例:ArrayDeque
の代わりにPriorityQueue
)。
有効期限の遅延がかなり大きい場合は、すべてのエントリを期限切れにして、永久に存続するはずのエントリをRemovalListener
に再挿入できます。これは2つの方法で失敗する可能性があります:1。その間にミスをする可能性があります。 2.削除と再挿入には、多くのCPU時間がかかる場合があります。
グアバに触発されたカフェインライブラリを使用できます。これはgithubリポジトリからの使用例です
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterCreate(5, TimeUnit.MINUTES)
明示的な無効化を使用して、削除するエントリを正確に定義できると思いますが、それはおそらくあなたが望むものではありません。
ただし、エントリに異なる重みを付けることができます。完璧ではありませんが、重要度の低いエントリを削除するようにキャッシュをガイドできます。 Weighter を参照してください。重みが0のエントリは、サイズベースの削除によって削除されません。