在Java中的每键阻塞映射 [英] Per-key blocking Map in Java

查看:127
本文介绍了在Java中的每键阻塞映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理一些第三方库代码,涉及创建昂贵的对象并将它们缓存在 Map 中。现有的实现类似于

I'm dealing with some third-party library code that involves creating expensive objects and caching them in a Map. The existing implementation is something like

lock.lock()
try {
    Foo result = cache.get(key);
    if (result == null) {
        result = createFooExpensively(key);
        cache.put(key, result);
    }
    return result;
} finally {
    lock.unlock();
}

显然这不是最好的设计 Foos 可以独立创建

Obviously this is not the best design when Foos for different keys can be created independently.

我当前的黑客是使用 code>地图 期货

My current hack is to use a Map of Futures:

lock.lock();
Future<Foo> future;
try {
    future = allFutures.get(key);
    if (future == null) {
        future = executorService.submit(new Callable<Foo>() {
            public Foo call() {
                return createFooExpensively(key);
            }
        });
        allFutures.put(key, future);
    }
} finally {
    lock.unlock();
}

try {
    return future.get();
} catch (InterruptedException e) {
    throw new MyRuntimeException(e);
} catch (ExecutionException e) {
    throw new MyRuntimeException(e);
}

但这似乎...有点黑客, p>

But this seems... a little hacky, for two reasons:


  1. 工作是在任意池线程上完成的。我很乐意在尝试获取特定键的第一个线程上完成
    的工作,特别是因为
    它将被阻止。

  2. 即使当 Map 完全填充,我们仍然通过 Future.get()获取
    结果。我希望这是很便宜,但它很丑。

  1. The work is done on an arbitrary pooled thread. I'd be happy to have the work done on the first thread that tries to get that particular key, especially since it's going to be blocked anyway.
  2. Even when the Map is fully populated, we still go through Future.get() to get the results. I expect this is pretty cheap, but it's ugly.

我想要的是替换 cache 使用 Map ,这将阻止获取给定键,直到该键具有值,但允许其他获取。有这样的事情存在吗?或者有人对 Map 的 Futures 有更清洁的替代方法吗?

What I'd like is to replace cache with a Map that will block gets for a given key until that key has a value, but allow other gets meanwhile. Does any such thing exist? Or does someone have a cleaner alternative to the Map of Futures?

推荐答案

为每个键创建锁定听起来很诱人,但它可能不是你想要的,特别是当键的数量很大时。

Creating a lock per key sounds tempting, but it may not be what you want, especially when the number of keys is large.

由于您可能需要为每个键创建一个专用(读写)锁,所以它会影响您的内存使用。此外,如果并发性真的很高,那么细粒度可能会达到递减递减的程度。

As you would probably need to create a dedicated (read-write) lock for each key, it has impact on your memory usage. Also, that fine granularity may hit a point of diminishing returns given a finite number of cores if concurrency is truly high.

ConcurrentHashMap通常是一种很好的解决方案,这个。它提供正常的读取器并发性(通常读取器不阻塞),并且更新可以并发达到所需的并发级别。这给你相当不错的可扩展性。上述代码可以用ConcurrentHashMap表示,如下所示:

ConcurrentHashMap is oftentimes a good enough solution in a situation like this. It provides normally full reader concurrency (normally readers do not block), and updates can be concurrent up to the level of concurrency level desired. This gives you pretty good scalability. The above code may be expressed with ConcurrentHashMap like the following:

ConcurrentMap<Key,Foo> cache = new ConcurrentHashMap<>();
...
Foo result = cache.get(key);
if (result == null) {
  result = createFooExpensively(key);
  Foo old = cache.putIfAbsent(key, result);
  if (old != null) {
    result = old;
  }
}

ConcurrentHashMap的直接使用有一个缺点,是多个线程可能发现密钥没有缓存,每个可以调用createFooExpensively()。因此,一些线程可能做丢弃工作。为了避免这种情况,你需要使用Java Concurrency in Practice中提到的memoizer模式。

The straightforward use of ConcurrentHashMap does have one drawback, which is that multiple threads may find that the key is not cached, and each may invoke createFooExpensively(). As a result, some threads may do throw-away work. To avoid this, you would want to use the memoizer pattern that's mentioned in "Java Concurrency in Practice".

但是,谷歌的好朋友已经解决了这些问题对于您以CacheBuilder的形式: http: //docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html

But then again, the nice folks at Google already solved these problems for you in the form of CacheBuilder: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html

LoadingCache<Key,Foo> cache = CacheBuilder.newBuilder().
  concurrencyLevel(32).
  build(new CacheLoader<Key,Foo>() {
    public Foo load(Key key) {
      return createFooExpensively(key);
    }
  });

...
Foo result = cache.get(key);

这篇关于在Java中的每键阻塞映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆