多线程对象→Java中的对象缓存映射?

时间:2010-02-27 18:47:12

标签: java multithreading collections caching

我想要一个Java集合:

  • 将任意Object映射到Object s(不是String或其他受限制的密钥)
  • 将用作缓存;如果密钥不在缓存中,则将计算一个值(这不必构建到集合中)
  • 将同时从多个线程访问
  • 永远不会删除项目
  • 必须非常高效阅读(缓存命中);写入效率不一定(缓存未命中)

如果在多个线程中同时缓存未命中导致冗余计算,则可以。典型的情况是缓存最初主要由一个线程填充。

线程不安全哈希表周围的synchronized块无法满足高效读取标准。线程局部缓存很简单,但意味着新线程很昂贵,因为它们有完整的缓存副本。

我们可以将Java 1.5内置函数或一个或多个类文件复制到我们的MIT许可项目中,而不是大型外部库。

4 个答案:

答案 0 :(得分:7)

使用java并发hashmap

ConcurrentHashMap<object, object> table;

public object getFromCache(object key)
{
    value = table.get(key);

    if (value == null)
    {
        //key isn't a key into this table, ie. it's not in the cache
        value = calculateValueForKey(key)
        object fromCache = table.putIfAbsent(key, value);
    }

    return value;
}

/**
* This calculates a new value to put into the cache
*/
public abstract object calculateValueForKey(object key);

N.b。这不再是多线程缓存的一般解决方案,因为它依赖于对象是不可变的所述事实,因此对象等价并不重要。

答案 1 :(得分:2)

这是我自己的解决方案的想法,但我不是线程编程方面的专家,所以请在适当的时候评论/投票/比较其他答案。

使用线程局部变量(java.lang.ThreadLocal),其中包含用作第一级缓存的每线程哈希表。如果在此表中找不到密钥,则对第二级高速缓存进行同步访问,该高速缓存是所有线程共享的synchronized - 访问哈希表。通过这种方式,缓存值的计算只进行一次,并且它在所有线程之间共享,但每个线程都有从键到值的映射的本地副本,因此存在一些内存成本(但低于独立的每线程缓存),但读取是有效的。

答案 2 :(得分:2)

我的一个项目中有关SingletonCache类的内容怎么样?

public abstract class SingletonCache<K, V> {

    private final ConcurrentMap<K, V> cache = new ConcurrentHashMap<K, V>();

    public V get(K key) {
        V value = cache.get(key);
        if (value == null) {
            cache.putIfAbsent(key, newInstance(key));
            value = cache.get(key);
        }
        return value;
    }

    protected abstract V newInstance(K key);
}

要使用它,您可以扩展它并实现newInstance方法,该方法在高速缓存未命中时创建新值。然后使用键调用get方法以获取与该键对应的实例。 Here是如何使用它的一个例子。

该类保证对于每个键只返回一个实例,但可以多次调用newInstance方法,在这种情况下使用第一个计算实例,其余的被丢弃。另请注意,此缓存不会删除旧实例,但会无限期地存储所有值(在我的用例中,必须缓存的实例数量有限)。从ConcurrentHashMap读取不使用锁定,因此它应该满足您的效率要求。

答案 3 :(得分:1)

Concurrency In PracticeBrian Goetz第5.6节中描述的缓存怎么样?概述了here

它只使用java.util.concurrent包中的类。

链接文章构建了一个缓存并描述了每个版本的弱点,直到最终版本是一个有效的缓存,其中只有一个并发线程将计算缺席条目。

我已经剪切并粘贴了下面的最终代码,但是值得一读文章并思考所概述的问题。甚至更好 - 买书 - 这很棒。

import java.util.concurrent.*;

public class Memoizer<A, V> implements Computable<A, V> {
  private final ConcurrentMap<A, Future<V>> cache
      = new ConcurrentHashMap<A, Future<V>>();
  private final Computable<A, V> c;
  public Memoizer(Computable<A, V> c) { this.c = c; }
  public V compute(final A 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) {
          // Kabutz: this is my addition to the code...
          try {
             throw e.getCause();
          } catch (RuntimeException ex) {
              throw ex;
          } catch (Error ex) {
              throw ex;
          } catch (Throwable t) {
              throw new IllegalStateException("Not unchecked", t);
          }
        }
     }
  }
}