我正在创建具有以下特征的记忆缓存:
什么会有更好的性能,或者在什么条件下一个解决方案会比另一个更受青睐?
线程本地哈希图:
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);
}
}
并发哈希地图:
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。这将导致有线的东西
使用并发HashMap,您必须管理它以防止mem泄漏
如果你坚持,我建议使用周或软参考,并在丰富的maxsize之后驱逐
如果你正在寻找一个内存缓存解决方案(不要重复轮子)尝试番石榴缓存http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html
这种计算非常昂贵
我假设这是您创建缓存的原因,这应该是您的主要关注点。
虽然解决方案的速度可能略有不同
简而言之,与多次计算同样的事情(对于多个线程)的成本相比,您解决方案的速度可能很小
请注意,您的并发HashMap实现不是线程安全的,可能会导致一个项目被计算两次。如果您直接存储结果而不使用显式锁定,实际上要做到这一点相当复杂,如果性能是一个问题,您当然希望避免这种情况。
值得注意的是,ConCurrentHashMap具有高度可扩展性,并且在高竞争下运行良好。我不知道ThreadLocal是否会表现得更好。
除了使用库之外,您还可以从实践清单5.19中Java并发中获得一些灵感。这个想法是为了拯救未来
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());
}
}
}
}