0

假设您有这种代码:

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()是一个昂贵的函数,那么一个调用者必须建立它的值将阻塞所有其他调用者,其值可能已经存在。我想找到一种机制,在这种机制中,调用者必须构建一个值不会阻塞其他调用者。

我不敢相信这个问题还没有得到解决(解决)。我试图用谷歌搜索解决方案,但到目前为止找不到。你有链接吗?

我正在考虑使用 a ReentrantReadWriteLock,但还没有任何东西。

4

3 回答 3

1

我认为部分问题是您拥有的 get 方法似乎是同步的。仅此一项就很难异步执行任何操作。看来您的 get 应该接受回调。

《Java 并发实践》一书详细介绍了使用 ConcurrentHashMap 和 FutureTasks 的出色缓存 - 查看http://jcip.net/listings/Memoizer.java

您仍然需要调整该类以使其异步 - 它仍然会在计算任务时阻塞当前线程,但它会阻止两个线程计算同一事物。如果一个线程已经在计算它并且另一个线程想要它,它将等到计算完成而不是开始新的计算

于 2012-06-17T15:24:09.660 回答
1

Guava有一个非常可靠的解决方案,基于 Doug Lea 的一些工作,他编写了大部分java.util.concurrent. (披露:我为 Guava 做出了贡献,尽管我根本没有从事缓存工作。)

Cache关于 Guava包的用户指南文章在这里,但语法看起来像这样......

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);
         }
       });
于 2012-06-17T15:49:26.953 回答
0

ReentrantReadWriteLock 可能是一个解决方案:从 map 获取数据时使用读锁,而在构建数据并将其放入 map 时使用写锁。这种方案的缺点是:写锁会锁住整个map,所以buildValue就变成了一个同步的方法,当写进map的缓存太多时,不得不一个一个地写。

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

于 2012-06-17T16:11:01.107 回答