そんなリザルトキャッシュ 
で大丈夫か? 
@making 槙 俊明 
2014-09-17
ここでいうリザルトキャッシュ 
とは 
•重い処理の結果を格納するメモ 
リ(キャッシュ) 
•リザルトキャッシュを再利用す 
ることで性能向上が期待できる
あなたのリザルトキャッシュ 
•スレッドセーフですか? 
•スケーラブルですか?
5-6 「効率的でスケーラブ 
ルなリザルトキャッシュを 
構築する」 
の内容をご紹介します
たまにみる実装
たまにみる実装 
static Map<BigInteger, List<BigInteger>> cache 
= new HashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
result = PrimeFactor.divide(n); // 素因数分解 
cache.put(n, result); 
} 
// … 
}
たまにみる実装 
static Map<BigInteger, List<BigInteger>> cache 
= new HashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
result = PrimeFactor.divide(n); // 素因数分解 
cache.put(n, result); 
} 
// … 
}
たまにみる実装 
static Map<BigInteger, List<BigInteger>> cache 
= new HashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
result = PrimeFactor.divide(n); // 素因数分解 
cache.put(n, result); 
} 
// … 
} 
キャッシュになかったら 
計算してキャッシュに追加

static Map<BigInteger, List<BigInteger>> cache 
= new HashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
result = PrimeFactor.divide(n); // 素因数分解 
cache.put(n, result); 
} 
// … 
} スレッドアンセーフ
よく見る実装 
static Map<BigInteger, List<BigInteger>> cache 
= new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
result = PrimeFactor.divide(n); // 素因数分解(重い処理) 
cache.put(n, result); 
} 
// … 
}
よく見る実装 
static Map<BigInteger, List<BigInteger>> cache 
= new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
スレッドセーフなMapに変更 
result = PrimeFactor.divide(n); // 素因数分解(重い処理) 
cache.put(n, result); 
} 
// … 
}
Demo

check 
get calc put 
check 
get calc put
check 
get calc put 
check 
get calc put
check 
get calc put 
check 
get calc put 
まだputされていない
static Map<BigInteger, List<BigInteger>> cache 
= new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.get(n); 
if (result == null) { 
result = PrimeFactor.divide(n); // 素因数分解(重い処理) 
cache.put(n, result); 
} 
// … 
} Atomicじゃない
FutureTaskを使って遅延評価
FutureTaskを使って遅延評価 
static Map<BigInteger, FutureTask<List<BigInteger>>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
FutureTask<List<BigInteger>> result = cache.get(n); 
if (result == null) { 
result = new FutureTask<>(() -> PrimeFactor.divide(n))); 
cache.put(n, result); 
result.run(); 
} 
// (略) result.get()で結果取得できるまでブロックする 
}
FutureTaskを使って遅延評価 
static Map<BigInteger, FutureTask<List<BigInteger>>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
FutureTask<List<BigInteger>> result = cache.get(n); 
if (result == null) { 
result = new FutureTask<>(() -> PrimeFactor.divide(n))); 
cache.put(n, result); 
result.run(); 
Callableで処理を記述 
} 
// (略) result.get()で結果取得できるまでブロックする 
}
FutureTaskを使って遅延評価 
static Map<BigInteger, FutureTask<List<BigInteger>>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
FutureTask<List<BigInteger>> result = cache.get(n); 
if (result == null) { 
result = new FutureTask<>(() -> PrimeFactor.divide(n))); 
cache.put(n, result); 
result.run(); 
Callableで処理を記述 
} 
// (略) result.get()で結果取得できるまでブロックする 
} 
キャッシュに入れてから処理実行
check 
get put calc 
check 
get put
Demo

check 
get put calc 
check 
get put 
calc
check 
get put calc 
check 
get put 
calc
check 
get put calc 
check 
get put 
calc 
新しいFutureTaskで上書きされた
check 
get put calc 
check 
get put 
calc 
結局2回計算 
新しいFutureTaskで上書きされた
FutureTaskを使って遅延評価 
static Map<BigInteger, FutureTask<List<BigInteger>>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
FutureTask<List<BigInteger>> result = cache.get(n); 
if (result == null) { 
result = new FutureTask<>(() -> PrimeFactor.divide(n))); 
cache.put(n, result); 
result.run(); 
} 
// (略) result.get()で結果取得できるまでブロックする 
} Atomicじゃない
ConcurrentMap#putIfAbsent
ConcurrentMap#putIfAbsent 
static ConcurrentMap<BigInteger, 
FutureTask<List<BigInteger>>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
FutureTask<List<BigInteger>> result = cache.get(n); 
if (result == null) { 
FutureTask<List<BigInteger>> ft = 
new FutureTask<>(() -> PrimeFactor.divide(n))); 
result = cache.putIfAbsent(n, result); 
if (result == null) {ft.run(); result = ft;} 
} 
// (略) result.get()で結果取得できるまでブロックする
ConcurrentMap#putIfAbsent 
static ConcurrentMap<BigInteger, 
FutureTask<List<BigInteger>>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
FutureTask<List<BigInteger>> result = cache.get(n); 
if (result == null) { 
FutureTask<List<BigInteger>> ft = 
new FutureTask<>(() -> PrimeFactor.divide(n))); 
result = cache.putIfAbsent(n, result); 
if (result == null) {ft.run(); result = ft;} 
} 
// (略) result.get()で結果取得できるまでブロックする 
キーが存在しない場合はnullを、 
存在する場合はそれを返す
check 
get pIA calc 
check 
get pIA 
pIA … putIfAbsent
check 
get pIA calc 
check 
get pIA 
pIA … putIfAbsent 
put済みなので上書きしない
Demo

でも面倒くさいね! 

Java SE 8から
Java SE 8から 
static ConcurrentHashMap<BigInteger, List<BigInteger>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.computeIfAbsent(n, 
(x) -> PrimeFactor.divide(x)); 
// … 
}
Java SE 8から 
static ConcurrentHashMap<BigInteger, List<BigInteger>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.computeIfAbsent(n, 
(x) -> PrimeFactor.divide(x)); 
// … 
} キーが存在しない場合はラムダ式 
の計算結果を返す
Java SE 8から 
static ConcurrentHashMap<BigInteger, List<BigInteger>> 
cache = new ConcurrentHashMap<>; 
void doGet(…) { 
BigInteger n = …; 
List<BigInteger> result = cache.computeIfAbsent(n, 
PrimeFactor::divide); 
// … 
} 
メソッド参照でOK
Demo
Cool! 

まとめ 
•ConcurrentHashMap#putIfAbsent + 
FutureTaskで効率的なリザルトキャッシュ 
を実装できる  
•JDK8からは 
ConcurrentHashMap#computeIfAbsent 
でおk 
Java SE 8を使おう
ご清聴 
ありがとうございました

そんなリザルトキャッシュで大丈夫か? #jjug

  • 1.
  • 2.
    ここでいうリザルトキャッシュ とは •重い処理の結果を格納するメモ リ(キャッシュ) •リザルトキャッシュを再利用す ることで性能向上が期待できる
  • 3.
  • 5.
    5-6 「効率的でスケーラブ ルなリザルトキャッシュを 構築する」 の内容をご紹介します
  • 6.
  • 7.
    たまにみる実装 static Map<BigInteger,List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … }
  • 8.
    たまにみる実装 static Map<BigInteger,List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … }
  • 9.
    たまにみる実装 static Map<BigInteger,List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … } キャッシュになかったら 計算してキャッシュに追加
  • 10.
  • 11.
    static Map<BigInteger, List<BigInteger>>cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … } スレッドアンセーフ
  • 12.
    よく見る実装 static Map<BigInteger,List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解(重い処理) cache.put(n, result); } // … }
  • 13.
    よく見る実装 static Map<BigInteger,List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { スレッドセーフなMapに変更 result = PrimeFactor.divide(n); // 素因数分解(重い処理) cache.put(n, result); } // … }
  • 14.
  • 15.
  • 16.
    check get calcput check get calc put
  • 17.
    check get calcput check get calc put
  • 18.
    check get calcput check get calc put まだputされていない
  • 19.
    static Map<BigInteger, List<BigInteger>>cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解(重い処理) cache.put(n, result); } // … } Atomicじゃない
  • 20.
  • 21.
    FutureTaskを使って遅延評価 static Map<BigInteger,FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); } // (略) result.get()で結果取得できるまでブロックする }
  • 22.
    FutureTaskを使って遅延評価 static Map<BigInteger,FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); Callableで処理を記述 } // (略) result.get()で結果取得できるまでブロックする }
  • 23.
    FutureTaskを使って遅延評価 static Map<BigInteger,FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); Callableで処理を記述 } // (略) result.get()で結果取得できるまでブロックする } キャッシュに入れてから処理実行
  • 24.
    check get putcalc check get put
  • 25.
  • 26.
  • 27.
    check get putcalc check get put calc
  • 28.
    check get putcalc check get put calc
  • 29.
    check get putcalc check get put calc 新しいFutureTaskで上書きされた
  • 30.
    check get putcalc check get put calc 結局2回計算 新しいFutureTaskで上書きされた
  • 31.
    FutureTaskを使って遅延評価 static Map<BigInteger,FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); } // (略) result.get()で結果取得できるまでブロックする } Atomicじゃない
  • 32.
  • 33.
    ConcurrentMap#putIfAbsent static ConcurrentMap<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { FutureTask<List<BigInteger>> ft = new FutureTask<>(() -> PrimeFactor.divide(n))); result = cache.putIfAbsent(n, result); if (result == null) {ft.run(); result = ft;} } // (略) result.get()で結果取得できるまでブロックする
  • 34.
    ConcurrentMap#putIfAbsent static ConcurrentMap<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { FutureTask<List<BigInteger>> ft = new FutureTask<>(() -> PrimeFactor.divide(n))); result = cache.putIfAbsent(n, result); if (result == null) {ft.run(); result = ft;} } // (略) result.get()で結果取得できるまでブロックする キーが存在しない場合はnullを、 存在する場合はそれを返す
  • 35.
    check get pIAcalc check get pIA pIA … putIfAbsent
  • 36.
    check get pIAcalc check get pIA pIA … putIfAbsent put済みなので上書きしない
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
    Java SE 8から static ConcurrentHashMap<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.computeIfAbsent(n, (x) -> PrimeFactor.divide(x)); // … }
  • 42.
    Java SE 8から static ConcurrentHashMap<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.computeIfAbsent(n, (x) -> PrimeFactor.divide(x)); // … } キーが存在しない場合はラムダ式 の計算結果を返す
  • 43.
    Java SE 8から static ConcurrentHashMap<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.computeIfAbsent(n, PrimeFactor::divide); // … } メソッド参照でOK
  • 44.
  • 45.
  • 46.
    まとめ •ConcurrentHashMap#putIfAbsent + FutureTaskで効率的なリザルトキャッシュ を実装できる  •JDK8からは ConcurrentHashMap#computeIfAbsent でおk 
  • 47.
  • 48.