1. キャッシュの基本特性
Guava Cacheはプロセス内ローカルキャッシュであり、ネットワーク通信を必要とせず高速なアクセスが可能ですが、JVMメモリ制限の影響を受けます。主な利用シーンは以下の通りです:
- 高頻度アクセスが求められる
- データ変更頻度が低い
- メモリ使用量が少ないデータ
- 全データの取得が必要
- リアルタイム性が必須でない
2. 基本的な実装例
2.1 ライブラリ依存関係
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
2.2 キャッシュインスタンスの生成
CacheBuilder<String, String> builder = CacheBuilder.newBuilder()
.initialCapacity(8)
.concurrencyLevel(5)
.maximumSize(15000)
.expireAfterWrite(30L, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchDataFromDB(key);
}
});
2.3 データ取得処理
// キャッシュ値を取得(存在しない場合ロード処理が自動実行)
String result = cache.get(key);
キーが存在しないか有効期限切れの場合、loadメソッドが呼び出されます。複数スレッドが同時にアクセスした場合、最初のスレッドがロードを実行し、他のスレッドは待機します。
3. リフレッシュ機能の実装
3.1 リフレッシュ設定例
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2));
Cache cache = CacheBuilder.newBuilder()
.expireAfterWrite(30L, TimeUnit.MINUTES)
.refreshAfterWrite(15L, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchFromDB(key);
}
@Override
public ListenableFuture<String> reload(String key, String oldValue) {
return executor.submit(() -> {
Thread.sleep(500);
return fetchFromDB(key);
});
}
});
3.2 時間設定の動作メカニズム
expireAfterWrite:設定時間経過後、キャッシュが削除されloadが実行refreshAfterWrite:設定時間経過後、reloadが非同期実行され、現時点の値が返却- 両方設定時は、
expireAfterWriteが優先
4. コアメカニズムの解説
4.1 キャッシュ構造
内部実装ではCacheSegment配列を管理し、各セグメントはAtomicReferenceArrayでハッシュテーブルを保持します。
class CacheSegment<K, V> extends ReentrantLock {
AtomicReferenceArray<CacheEntry<K, V>> table;
}
4.2 過去データの処理
有効期限の判定はisExpiredメソッドで行われます:
boolean isExpired(ReferenceEntry entry, long now) {
if (expiresAfterWrite() && (now - entry.getWriteTime() >= expireAfterWriteNanos)) {
return true;
}
return false;
}
5. リスク対策
5.1 キャッシュパンク対策
複数スレッドによる同時アクセスを防ぐため、expireAfterWriteとrefreshAfterWriteを組み合わせて利用します。有効期限切れ時に1つのスレッドのみがロード処理を実行し、他のスレッドは待機します。
5.2 リフレッシュ時のデータ不整合
reloadは非同期実行されるため、返却値がnullの場合、古い値が返却されます。このため、リフレッシュ中は古いデータが一時的に使用される可能性があります。