[英]ThreadLocal HashMap vs ConcurrentHashMap for thread-safe unbound caches
我正在創建一個具有以下特征的memoization緩存:
什么會有一個優越的性能,或在什么條件下一個解決方案優於另一個解決方案?
ThreadLocal HashMap:
class MyCache {
private static class LocalMyCache {
final Map<K,V> map = new HashMap<K,V>();
V get(K key) {
V val = map.get(key);
if (val == null) {
val = computeVal(key);
map.put(key, val);
}
return val;
}
}
private final ThreadLocal<LocalMyCache> localCaches = new ThreadLocal<LocalMyCache>() {
protected LocalMyCache initialValue() {
return new LocalMyCache();
}
};
public V get(K key) {
return localCaches.get().get(key);
}
}
ConcurrentHashMap的:
class MyCache {
private final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<K,V>();
public V get(K key) {
V val = map.get(key);
if (val == null) {
val = computeVal(key);
map.put(key, val);
}
return val;
}
}
我認為如果有很多線程,ThreadLocal解決方案最初會因為每個線程的所有緩存未命中而緩慢,但是超過數千次讀取,攤銷成本將低於ConcurrentHashMap解決方案。 我的直覺是否正確?
還是有更好的解決方案?
使用ThreadLocal作為緩存是一個不好的做法
在大多數容器中,線程通過線程池重用,因此永遠不會是gc。 這會帶來一些有線的東西
使用ConcurrentHashMap你必須管理它以防止內存泄漏
如果你堅持,我建議使用周或軟參考並在富有maxsize之后逐出
如果您正在尋找內存緩存解決方案(不要重新發明輪子),請嘗試使用guava緩存http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html
這個計算非常昂貴
我認為這是你創建緩存的原因,這應該是你最關心的問題。
雖然解決方案的速度可能略有不同<< 100 ns,但我懷疑能夠在線程之間共享結果更為重要。 即ConcurrentHashMap可能是您的應用程序的最佳選擇,因為從長遠來看,它可能會為您節省更多的CPU時間。
簡而言之,與多次計算同一事物的成本相比,您的解決方案的速度可能很小(對於多個線程)
請注意,ConcurrentHashMap實現不是線程安全的,可能導致一個項目被計算兩次。 如果直接存儲結果而不使用顯式鎖定,那么實現它是非常復雜的,如果性能是一個問題,你當然希望避免這種情況。
值得注意的是ConcurrentHashMap具有高度可擴展性,並且在高爭用下運行良好。 我不知道ThreadLocal是否會表現更好。
除了使用庫之外,您還可以從實踐清單5.19中的Java Concurrency中獲得一些靈感。 這樣做是為了節省Future<V>
在你的地圖,而不是V
。 這有助於在保持高效(無鎖)的同時使整個方法線程安全。 我粘貼下面的實現以供參考,但本章值得一讀,以了解每個細節都很重要。
public interface Computable<K, V> {
V compute(K arg) throws InterruptedException;
}
public class Memoizer<K, V> implements Computable<K, V> {
private final ConcurrentMap<K, Future<V>> cache = new ConcurrentHashMap<K, Future<V>>();
private final Computable<K, V> c;
public Memoizer(Computable<K, V> c) {
this.c = c;
}
public V compute(final K arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
ft.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
}
}
鑒於實現這兩者相對容易,我建議你嘗試它們並在穩態負載下進行測試,看看哪一個對你的應用程序表現最佳。
我的猜測是ConcurrentHashMap
會快一點,因為它不必像ThreadLocal
那樣對Thread.currentThread()
進行本機調用。 但是,這可能取決於您正在存儲的對象以及它們的哈希編碼的效率。
我可能還值得嘗試將並發映射的concurrencyLevel
調整為您需要的線程數。 默認為16。
兩種解決方案中的查找速度可能相似。 如果沒有其他問題,我更喜歡ThreadLocal,因為多線程問題的最佳解決方案是單線程。
但是,您的主要問題是您不希望同一個鍵的並發計算; 所以每個鍵應該有一個鎖; 這種鎖通常可以通過ConcurrentHashMap實現。
所以我的解決方案就是
class LazyValue
{
K key;
volatile V value;
V getValue() { lazy calculation, doubled-checked locking }
}
static ConcurrentHashMap<K, LazyValue> centralMap = ...;
static
{
for every key
centralMap.put( key, new LazyValue(key) );
}
static V lookup(K key)
{
V value = localMap.get(key);
if(value==null)
localMap.put(key, value=centralMap.get(key).getValue())
return value;
}
性能問題無關緊要,因為解決方案不相同。
線程之間不共享ThreadLocal哈希映射,因此線程安全問題甚至不會出現,但它也不符合您的規范,而這些規范並沒有說明每個線程都有自己的緩存。
對線程安全的要求意味着在所有線程之間共享一個緩存,這完全排除了ThreadLocal。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.