Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/319.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 线程安全未绑定缓存的ThreadLocal HashMap与ConcurrentHashMap_Java_Performance_Caching_Thread Local_Concurrenthashmap - Fatal编程技术网

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%以上的缓存命中率
  • 必须是线程安全的
什么会有更好的性能,或者在什么条件下一种解决方案会优于另一种

ThreadLocal哈希映射:

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(