仅在尚未存在时创建并放置映射值,并获取它:线程安全实现

sp0*_*00m 8 java multithreading map

使这个片段线程安全的最佳方法是什么?

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
}
Run Code Online (Sandbox Code Playgroud)

以下是我能想到的几个解决方案:

  1. 我可以使用a ConcurrentHashMap,但如果我很好理解,它只是使原子putget操作线程安全,即它不能确保对buildB()给定值只调用一次方法.
  2. 我可以使用Collections.synchronizedMap(new HashMap<A, B>()),但我会遇到与第一点相同的问题.
  3. 我可以设置整个putIfNeededAndGet()方法synchronized,但我可以有很多线程一起访问这个方法,所以它可能非常昂贵.
  4. 我可以使用双重检查锁定模式,但仍然存在相关的无序写入问题.

我可以提供哪些其他解决方案?

我知道这是一个很常见的网络主题,但我还没有找到一个清晰,完整和有效的例子.

Evg*_*eev 5

使用ConcurrentHashMap和您使用的延迟初始化模式

public static B putIfNeededAndGet(A key) {
    B value = map.get(key);
    if (value == null) {
        value = buildB(...);
        B oldValue = map.putIfAbsent(key, value);
        if (oldValue != null) {
             value = oldValue;
        }
    }
    return value;
}
Run Code Online (Sandbox Code Playgroud)

  • +1.我不会过度工程并保持简单.在这种情况下可能发生的最糟糕的事情是`buildB()`将被多次调用. (2认同)

Joa*_*uer 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);
         }
       });
Run Code Online (Sandbox Code Playgroud)

您还可以轻松添加定时过期和其​​他功能。

此缓存将确保load()(或在您的情况下buildB)不会与相同的同时调用key。如果一个线程已经在构建 a B,那么任何其他调用者将只等待该线程。