并发 LRU 缓存实现

2022-09-03 14:46:46

我需要线程安全高效的缓存实现代码。下面的代码不是线程安全的。此代码是否可以使用 增强。提前致谢。LRUConcurrentHashMap

private class LruCache<A, B> extends LinkedHashMap<A, B> {
    private final int maxEntries;

    public LruCache(final int maxEntries) {
        super(maxEntries + 1, 1.0f, true);
        this.maxEntries = maxEntries;
    }

    @Override
    protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
        return super.size() > maxEntries;
    }
}

答案 1

你能做的最好的事情就是让它成为线程安全的,就像javadoc中解释的那样,用 Collections.synchronizedMap(map) 包装它:

请注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部同步该映射。这通常是通过对自然封装映射的某些对象进行同步来实现的。如果不存在此类对象,则应使用该方法“包装”映射。这最好在创建时完成,以防止意外地对地图进行不同步访问:Collections.synchronizedMap

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

但是,仅仅让它完全线程安全是不够的,您需要使用包装的映射的实例作为对象的监视器来保护对映射内容的任何迭代:

Map m = Collections.synchronizedMap(map);
...
Set s = m.keySet();  // Needn't be in synchronized block
...
synchronized (m) {  // Synchronizing on m, not s!
    Iterator i = s.iterator(); // Must be in synchronized block
    while (i.hasNext())
      foo(i.next());
}

这几乎是您使用我们开箱即用的功能轻松完成的所有操作,如果您想要线程安全且更高效的东西,那么您应该查看Google GuavaCacheJDK

下面是一个 LRU 缓存的示例,最大大小为 2,使用番石榴构建:

ConcurrentMap<String, String> cache = 
    CacheBuilder.newBuilder()
        .maximumSize(2L)
        .<String, String>build().asMap();
cache.put("a", "b");
cache.put("b", "c");
System.out.println(cache);
cache.put("a", "d");
System.out.println(cache);
cache.put("c", "d");
System.out.println(cache);

输出:

{b=c, a=b}
{b=c, a=d}
{c=d, a=d}

答案 2

找到番石榴的缓存。我自己没有用过。

缓存类似于 ConcurrentMap,但并不完全相同。

资料来源:https://github.com/google/guava/wiki/CachesExplained

例:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .expireAfterAccess(10, TimeUnit.MINUTES)
       .maximumSize(1000)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) { // no checked exception
               return createExpensiveGraph(key);
             }
           });

...
return graphs.getUnchecked(key);