使此代码段具有线程安全性的最佳方法是什么?

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
}

以下是我可以考虑的一些解决方案:
  • 我可以使用ConcurrentHashMap,但是如果我很好理解,它只会使原子putget操作具有线程安全性,即,它不能确保给定值的buildB()方法仅被调用一次。
  • 我可以使用Collections.synchronizedMap(new HashMap<A, B>()),但是我会遇到与第一点相同的问题。
  • 我可以将整个putIfNeededAndGet()方法设置为synchronized,但是我真的可以有很多线程一起访问此方法,因此它可能会非常昂贵。
  • 我可以使用双重检查的锁定模式,但是仍然存在相关的out-of-order writes issue

  • 我还能有什么其他解决方案?

    我知道这是Web上很常见的话题,但是我还没有找到一个清晰,完整且有效的示例。

    最佳答案

    这可能不是您要查找的答案,但是使用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)。如果一个线程已经在构建B,则任何其他调用者都将等待该线程。

    09-16 03:09