2

Javaでは、次のようなことをしたい:

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

コードのスニペットをマルチスレッド環境で実行できるようになりました。 mapConcurrentHashMap にすることができます。

しかし、そのロジックをアトミックにするにはどうすればよいでしょうか。

「同期」ブロックのような些細な解決策を私に与えないでください。この問題は、きっぱりときちんと解決できると思います。

4

6 に答える 6

6

それはGuavaによってきちんと解決されました。

を使用しCacheBuilder呼び出します。これはオブジェクトを返します。本当に実装が必要な場合は、 を呼び出すことができます。buildCacheLoaderLoadingCacheMapasMap()

MapMakerその古いものもありますが、アプローチmakeComputingMapを支持して廃止されました。CacheBuilder

もちろん、手動で実装することもできますが、それを正しく行うことは簡単ではありません。考慮すべきいくつかの側面は次のとおりです。

  • create同じ入力で2回呼び出すのを避けたい
  • 現在のスレッドが作成を完了するのを待ちたいが、アイドルループでそれをしたくない
  • 適切な場合 (つまり、要素が既にマップ内にある場合) には同期を避けたいと考えています。
  • 2 つの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 に答える
0

解決策は実際には並行性で文書化されていると思います。秘訣は、マップのオブジェクトとしてRの代わりにFutureを使用することです。

あまりにも複雑に見えるので、私はこの答えが嫌いですが。

コードは次のとおりです。

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 に答える
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

これはあなたが探しているものではないかもしれませんが、議論のために含めます.

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 に答える