提问者:小点点

线程安全的未绑定缓存的ThreadLocal HashMap与并发HashMap


我正在创建具有以下特征的记忆缓存:

  • 缓存未命中将导致计算和存储条目
    • 这个计算非常昂贵
    • 这个计算是幂等的
    • 输入最多会产生500个条目
    • 每个存储的条目都非常小
    • 缓存的寿命相对较短(通常不到一个小时)
    • 总体而言,内存使用不是问题

    什么会有更好的性能,或者在什么条件下一个解决方案会比另一个更受青睐?

    线程本地哈希图:

    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解决方案。我的直觉正确吗?

    或者有更好的解决方案吗?


共3个答案

匿名用户

使用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());
            }
        }
    }
}

相关问题