8

このスニペットをスレッドセーフにする最善の方法は何ですか?

private static final Map<A, B> MAP = new HashMap<A, B>();

public static B putIfNeededAndGet(A key) {
    B value = MAP.get(key);
    if (value == null) {
        value = buildB(...);
        MAP.put(key, value);
    }
    return value;
}

private static B buildB(...) {
    // business, can be quite long
}

私が考えることができるいくつかの解決策は次のとおりです。

  1. を使用することもできますがConcurrentHashMap、よく理解していれば、アトミックおよび操作がスレッドセーフになるだけです。つまり、特定の値に対してメソッドが 1 回だけ呼び出されることは保証されませputgetbuildB()
  2. を使用できますCollections.synchronizedMap(new HashMap<A, B>())が、最初のポイントと同じ問題が発生します。
  3. putIfNeededAndGet()メソッド全体を設定することもsynchronizedできますが、非常に多くのスレッドがこのメソッドに一緒にアクセスする可能性があるため、非常にコストがかかる可能性があります。
  4. 二重チェックのロック パターンを使用できますが、関連する順不同の書き込みの問題がまだあります。

他にどのような解決策がありますか?

これが Web で非常に一般的なトピックであることは知っていますが、明確で完全で実際に機能する例をまだ見つけていません。

4

4 に答える 4

2

これはあなたが探している答えではないかもしれませんが、Guava CacheBuilderを使用してください。

private static final LoadingCache<A, B> CACHE = CacheBuilder.newBuilder()
   .maximumSize(100) // if necessary
   .build(
       new CacheLoader<A, B>() {
         public B load(A key) {
           return buildB(key);
         }
       });

また、期限付きの有効期限やその他の機能も簡単に追加できます。

このキャッシュにより、load()(またはあなたの場合buildBは)同じ と同時に呼び出されないことが保証されますkey。1 つのスレッドが既に を構築している場合B、他の呼び出し元はそのスレッドを待つだけです。

于 2013-11-13T09:36:07.250 に答える
2

上記のソリューションでは、多くのスレッドがprocessB(...)同時にクラスを作成する可能性があるため、すべてが計算されます。しかし、私の場合、私は使用Futureしていて、単一のスレッドのみが古い値を取得nullするため、processB残りは待機するだけf.get()です。

 private static final ConcurrentMap<A, Future<B>> map = new ConcurrentHashMap<A, Future<B>>();
public static B putIfNeededAndGet(A key) {
    while (true) {
        Future<V> f = map.get(key);
        if (f == null) {
            Callable<B> eval = new Callable<V>() {
                public B call() throws InterruptedException {
                    return buildB(...);
                }
            };
            FutureTask<V> ft = new FutureTask<V>(eval);
            f = map.putIfAbsent(arg, ft);
            if (f == null) {
                f = ft;
                ft.run();
            }
        }
        try {
            return f.get();
        } catch (CancellationException e) {
            cache.remove(arg, f);
        } catch (ExecutionException e) {

        }
    }

}
于 2013-11-13T09:46:54.270 に答える
1

おそらくこれは他の人にも役立つと思います.Java 8ラムダを使用して、私にとってはうまく機能するこの関数を作成しました:

private <T> T getOrCreate(Object key, Map<Object, T> map,
                                Function<Object, T> creationFunction) {
    T value = map.get(key);

    // if the doesn't exist yet - create and add it
    if (value == null) {
        value = creationFunction.apply(key);
        map.put(label, metric);
    }
    return value;
}

次に、次のように使用できます。

Object o = getOrCreate(key, map, s -> createSpecialObjectWithKey(key));

私は特定のもののためにこれを作成しましたが、コンテキストとコードをより一般的な外観に変更しました.

また、オブジェクトをジェネリック型に変更することで、さらにジェネリック化することもできます。不明な場合はお知らせください。別の例を追加します。

アップデート:

基本的に同じことをするMap.computeIfAbsentについて知りました。Java 8が大好きです:)

于 2016-03-22T18:58:08.157 に答える