SelfPopulatingCache

自前でキャッシュを行う場合、データを取得する際のお決まりのパターンってありますよね。キャッシュミスした場合の処理って、大体こんな感じじゃないでしょうか。

  1. キャッシュからgetを試みるが、nullが返る。
  2. データソースからデータを取得。
  3. キャッシュにput。
  4. データを返す。

毎回こんなコード書くのは時間の無駄です。そこで登場するのが SelfPopulatingCacheです。

特徴は3つあります。

  • BlockingCacheの特徴を継承。(実際に継承してるので)
  • キャッシュを使う側は、キャッシュの対象データの置き場所について一切知る必要がない。(CacheEntryFactoryで対応)
  • refresh()メソッドが用意されており、このメソッドが呼び出されると、キャッシュ内の全要素を最新のものにアップデートします。その際、refresh中にほかのスレッドがキャッシュを読みにきた場合は、refresh()終了までブロックされることなく、古いデータを即時返します。(refresh()内部でのput()中はブロックしますが)

以下は使用例。

// シングルトンの CacheManager を取得する。
CacheManager singletonManager = CacheManager.create();

// testCache という名前のキャッシュを作成して singletonManager に追加する。
singletonManager.addCache("testCache");

// キャッシュを取得する。
Ehcache test = singletonManager.getCache("testCache");

// データソースの作成。
final Map<String, String> source = new HashMap<String, String>();
source.put("1", "foo");

// CacheEntryFactory を作成して、データソースからのデータ取得方法を記述する。
CacheEntryFactory cef = new CacheEntryFactory(){
	public Object createEntry(Object key) throws Exception {
		Thread.sleep(200); // データを作るのに200msかかることにする。
		return source.get(key);
	}
};

// SelfPopulatingCache を作成。
final SelfPopulatingCache spc = new SelfPopulatingCache(test, cef);

// キャッシュからデータをget
Element result = spc.get("1");

// 内容確認。
System.out.println("キャッシュに明示的に put() してないのに、自動的に値が入っている : " + result.getObjectValue());

// データソースの内容が変更される。
source.put("1", "bar");
System.out.println("データソースを更新! foo -> bar");

// キャッシュからデータをget。キャッシュの内容は古いまま。
result = spc.get("1");
System.out.println("データソースは更新したけど、キャッシュは古いまま : " + result.getObjectValue());

// キャッシュをリフレッシュ
spc.refresh();
System.out.println("キャッシュをリフレッシュ");

// キャッシュからデータをget。キャッシュ内容が更新されている。
result = spc.get("1");
System.out.println("キャッシュ内容が更新されている : " + result.getObjectValue());

// データソースの内容がさらに変更される。
source.put("1", "hoge");

// 2つのスレッドを走らせ、1つめのスレッドでは refresh を行い、2つめのスレッドではrefresh処理中のキャッシュからデータを読む。
new Thread(new Runnable(){public void run() { 
	System.out.println("[Thread1] refresh start.");
	spc.refresh();
	System.out.println("[Thread1] refresh complete!");
}}).start();
Thread.sleep(100);
new Thread(new Runnable(){public void run() { 
	// Blockingしない
	System.out.println("[Thread2] refresh中なので、古い値 : " + spc.get("1").getObjectValue());
}}).start();	
Thread.sleep(200);

// キャッシュからデータをget。新しい内容が表示される。
result = spc.get("1");
System.out.println("新しい値 : " + result.getObjectValue());

実行結果

キャッシュに明示的に put() してないのに、自動的に値が入っている : foo
データソースを更新! foo -> bar
データソースは更新したけど、キャッシュは古いまま : foo
キャッシュをリフレッシュ
キャッシュ内容が更新されている : bar
[Thread1] refresh start.
[Thread2] refresh中なので、古い値 : bar
[Thread1] refresh complete!
新しい値 : hoge