在Java中构建一个缓存对象,“异步”(种类):howto?

时间:2012-06-17 15:17:47

标签: java concurrency

假设你有这种代码:

public final class SomeClass
{
    private final Map<SomeKey, SomeValue> map = new HashMap<SomeKey, SomeValue>();

    // ...

    public SomeValue getFromCache(final SomeKey key)
    {
        SomeKey ret;
        synchronized(map) {
            ret = map.get(key);
            if (ret == null) {
                ret = buildValue(key);
                map.put(key, ret);
            }
        }
        return ret;
    }

 //etc
 }

问题在于性能:如果buildValue()是一个昂贵的函数,那么一个必须构建其值的调用者将阻止其值可能已经存在的所有其他调用者。我想找到一种机制,其中必须构建值的调用者阻止其他调用者。

我无法相信这个问题已经解决(已解决)。我试图谷歌寻找解决方案但到目前为止找不到。你有链接吗?

我正在考虑使用ReentrantReadWriteLock,但还没有提供任何内容。

3 个答案:

答案 0 :(得分:1)

我认为部分问题是你拥有的get方法似乎是同步的。仅这一点就很难做任何异步。看来你的获取应该接受回调。

Java Concurrency in Practice一书使用ConcurrentHashMap和FutureTasks详细介绍了一个很棒的缓存 - 请查看http://jcip.net/listings/Memoizer.java

你仍然需要调整该类以使其异步 - 它仍然会在计算任务时阻塞当前线程,但它会阻止两个线程计算相同的东西。如果一个线程已经在计算它而另一个想要它,那么它将等到计算结束而不是开始新的计算

答案 1 :(得分:1)

Guava基于Doug Lea撰写的大部分java.util.concurrent的一些作品,有一个非常可靠的解决方案。 (披露:我为Guava做出了贡献,虽然我根本没有进行过缓存。)

关于Guava的Cache包的用户指南文章是here,但语法如下所示......

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
   .maximumSize(1000)
   .expireAfterWrite(10, TimeUnit.MINUTES)
   .removalListener(MY_LISTENER)
   .build(
       new CacheLoader<Key, Graph>() {
         public Graph load(Key key) throws AnyException {
           return createExpensiveGraph(key);
         }
       });

答案 2 :(得分:0)

ReentrantReadWriteLock可能是一个解决方案:从地图获取数据时使用读锁定,而在构建数据时使用写锁定并将其放入映射中。这个解决方案的缺点是:写锁定将锁定整个映射,因此buildValue成为一个同步方法,当太多的缓存写入映射时,它们必须逐个写入。

另一种方法是使用 java.util.concurrent.ConcurrentMap ,然后在将数据放入地图时,不需要使用 putIfAbsent 进行任何锁定。缺点是可能在不同的线程中同时构建具有相同键的数据,但除非您的应用需要严格的内存使用,否则它不会成为问题。