2

在Java中,我想做这样的事情:

   Object r = map.get(t);
   if (r == null) {
      r = create(); // creating r is an expensive operation.
      map.put(t, r);  
   }

现在该代码片段可以在多线程环境中执行。 map可以是 ConcurrentHashMap。

但是我如何使这个逻辑原子化呢?

请不要给我像“同步”块这样的琐碎解决方案。我希望这个问题可以一劳永逸地解决。

4

6 回答 6

6

Guava巧妙地解决了这个问题。

使用CacheBuilder调用。build_ 这将返回一个对象。如果你真的需要一个实现,你可以调用.CacheLoaderLoadingCacheMapasMap()

也有较旧MapMakermakeComputingMap,但不推荐使用这种CacheBuilder方法。

当然,您也可以手动实现它,但正确地做到这一点并非易事。需要考虑的几个方面是:

  • 您想避免create使用相同的输入调用两次
  • 您想等待当前线程完成创建,但不想使用空闲循环执行此操作
  • 你想避免在好的情况下同步(即元素已经在地图中)。
  • 如果两个create呼叫同时发生,您希望每个呼叫者只等待与他相关的呼叫。
于 2012-12-05T12:43:18.687 回答
4

尝试

    value = concurentMap.get(key);
    if(value == null) {
        map.putIfAbsent(key, new Value());
        value = map.get(key);
    }
    return value;
于 2015-02-04T12:26:10.890 回答
1

从 Java 8 开始,ConcurrentMap.computeIfAbsent您正在寻找方法:相当于此映射的以下步骤,但是是原子的:

 V oldValue = map.get(key);
 if (oldValue == null) {
     V newValue = mappingFunction.apply(key);
     if (newValue != null) {
         return map.putIfAbsent(key, newValue);
     } else {
         return null;
     }
 } else {
     return oldValue;
 }

最常见的用法是构造一个新对象作为初始映射值或记忆结果,我认为这就是您要查找的内容,如下所示:

 Value v = map.computeIfAbsent(key, k -> new Value(f(k)));
于 2020-03-03T13:36:23.440 回答
0

我知道这可能不是你要找的,但为了争论,我会把它包括在内。

public Object ensureExistsInMap(Map map, Object t) {

    Object r = map.get(t);
    if (r != null) return r; // we know for sure it exists

    synchronized (creationLock) {
        // multiple threads might have come this far if r was null
        // outside the synchronized block
        r = map.get(t); 
        if (r != null) return r;

        r = create();
        map.put(t, r);

        return r;
    }
}
于 2012-12-05T12:52:07.020 回答
0

您所描述的基本上是具有延迟初始化的多音模式

这是一个使用现代 Java 锁的双重锁定的示例

private static Map<Object, Object> instances = new ConcurrentHashMap<Object, Object>();
private static Lock createLock = new ReentrantLock();

private Multitone() {}

public static Object getInstance(Object key) {
    Object instance = instances.get(key);
    if (instance == null) {
        createLock.lock();
        try {
            if (instance == null) {
                instance = createInstance();
                instances.put(key, instance);
            }
        } finally {
            createLock.unlock();
        }
    }
    return instance;
}
于 2012-12-05T12:58:05.540 回答
0

我认为解决方案在实践中以并发记录。诀窍是使用 Future 而不是 R 作为地图中的对象。

虽然我不喜欢这个答案,因为它看起来太复杂了。

这是代码:

public class Memorizer<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 Memorizer(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) {
          throw launderThrowable(e.getCause());
       }
    }
}
于 2012-12-05T13:54:33.417 回答