Java 线程安全未绑定缓存的ThreadLocal HashMap与ConcurrentHashMap
我正在创建具有以下特征的备忘录缓存:Java 线程安全未绑定缓存的ThreadLocal HashMap与ConcurrentHashMap,java,performance,caching,thread-local,concurrenthashmap,Java,Performance,Caching,Thread Local,Concurrenthashmap,我正在创建具有以下特征的备忘录缓存: 缓存未命中将导致计算和存储条目 这种计算非常昂贵 这个计算是幂等的 无界(从未删除条目),因为: 输入将导致最多500个条目 每个存储条目都非常小 缓存的寿命相对较短(通常不到一小时) 总的来说,内存使用不是问题 在缓存的生命周期内,将有数千次读取,我预计99.9%以上的缓存命中率 必须是线程安全的 什么会有更好的性能,或者在什么条件下一种解决方案会优于另一种 ThreadLocal哈希映射: class MyCache { privat
- 缓存未命中将导致计算和存储条目
- 这种计算非常昂贵
- 这个计算是幂等的
- 无界(从未删除条目),因为:
- 输入将导致最多500个条目
- 每个存储条目都非常小
- 缓存的寿命相对较短(通常不到一小时)
- 总的来说,内存使用不是问题
- 在缓存的生命周期内,将有数千次读取,我预计99.9%以上的缓存命中率
- 必须是线程安全的
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);
}
}
类MyCache{
私有静态类LocalMyCache{
final Map=new HashMap();
V get(K键){
V val=map.get(键);
if(val==null){
val=计算值(键);
地图放置(键,val);
}
返回val;
}
}
private final ThreadLocal localCaches=new ThreadLocal(){
受保护的LocalMyCache初始值(){
返回新的LocalMyCache();
}
};
公共V get(K键){
返回localCaches.get().get(键);
}
}
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;
}
}
类MyCache{
私有最终ConcurrentHashMap=新ConcurrentHashMap();
公共V get(K键){
V val=map.get(键);
if(val==null){
val=计算值(键);
地图放置(键,val);
}
返回val;
}
}
我认为ThreadLocal解决方案最初会因为每个线程的所有缓存未命中而变得更慢,但在数千次读取之后,摊销成本将低于ConcurrentHashMap解决方案。我的直觉正确吗
或者有更好的解决方案吗?使用ThreadLocal作为缓存不是一个好的做法 在大多数容器中,线程是通过线程池重用的,因此从来不是gc。这将导致一些有线的东西 使用ConcurrentHashMap您必须对其进行管理,以防止内存泄漏 如果您坚持,我建议使用week或soft ref,并在rich maxsize之后逐出 如果您正在寻找内存缓存解决方案(不要重新发明轮子) 试试番石榴
鉴于实现这两种方法相对容易,我建议您尝试这两种方法,并在稳态负载下进行测试,以确定哪种方法对您的应用程序性能最好 我的猜测是,
ConcurrentHashMap
会更快一些,因为它不必像ThreadLocal
那样对Thread.currentThread()
进行本机调用。但是,这可能取决于您存储的对象及其哈希编码的效率
我可能还值得尝试将并发映射的concurrentylevel
调整到您需要的线程数。它默认为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;
}
类懒散值
{
K键;
挥发性V值;
V getValue(){延迟计算,双重检查锁定}
}
静态ConcurrentHashMap centralMap=。。。;
静止的
{
每把钥匙
centralMap.put(键,新懒散值(键));
}
静态V查找(K键)
{
V value=localMap.get(键);
如果(值==null)
localMap.put(key,value=centralMap.get(key.getValue())
返回值;
}
请注意,您的ConcurrentHashMap实现不是线程安全的,可能导致一个项目被计算两次。如果直接存储结果而不使用显式锁定,那么要获得正确的结果实际上是相当复杂的,如果性能是一个问题,您当然希望避免显式锁定
值得注意的是,ConcurrentHashMap具有高度的可扩展性,在高争用性下工作良好。我不知道ThreadLocal是否会表现得更好
除了使用图书馆,你还可以从中获得一些灵感。其想法是在地图中保存一个未来
,而不是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());
}
}
}
}
公共接口可计算{
V compute(K arg)抛出中断异常;
}
公共类备忘录生成器实现可计算{
私有最终ConcurrentMap缓存=新ConcurrentHashMap();
私有最终可计算c;
公共记忆器(可计算c){
这个.c=c;
}
公共V计算(最终K参数)抛出InterruptedException{
while(true){
Future f=cache.get(arg);
如果(f==null){
Callable eval=新的Callable(){
public V call()抛出InterruptedException{
返回c.compute(arg);
}
};
FutureTask ft=新的FutureTask(eval);
f=缓存.putIfAbsent(arg,ft);
如果(f==null){
f=英尺;
ft.run();
}
}
试一试{
返回f.get();
}捕获(取消异常e){
cache.remove(