ConcurrentHashMapcomputeIfAbsent 判断是否是第一次

Eug*_*ene 8 java concurrency concurrenthashmap java-17

对我来说,要为此给出一个合适的标题很复杂。但一个例子应该会让事情变得简单得多。假设我有这个:

final class Cache {
   private static final ConcurrentHashMap<String, List<String>> CACHE = ...

   static List<String> byName(String name) {
      return CACHE.computeIfAbsent(name, x -> // some expensive operation)
   }

}
Run Code Online (Sandbox Code Playgroud)

这个想法可能很简单,它充当 LoadingCache,很像番石榴或咖啡因(实际上它更复杂,但这与问题无关)。

我希望能够判断这是第一次加载到缓存中,还是读取现有映射。目前,我这样做:

final class Cache {
   private static final ConcurrentHashMap<String, List<String>> CACHE = ...

   static List<String> byName(String name) {
      boolean b[] = new boolean[1];
      List<String> result = CACHE.computeIfAbsent(name, x -> {
            b[0] = true;
            // some expensive operation)
      });

      if(b[0]) {
         // first load into the cache, do X
      } else {
         // do Y
      }

      return result;
   }

}
Run Code Online (Sandbox Code Playgroud)

这可行,但我担心我缺少一些ConcurrentHashMap可以为我提供的东西,让我可以做同样的事情。谢谢。

kni*_*ttl 3

如果您想避免单元素数组将数据传递出 lambda(我宁愿使用 or 来完成AtomicReferenceAtomicBoolean,您可以使用有状态回调对象。它不会改变代码的行为或设计,但可以被认为更干净、更面向 OOP。

class LoadingAction<K, V> {
  private boolean called = false;

  public V load(final K key) {
    called = true;
    // load data
    return ...;
  }

  public void executePostLoad() {
    if (called) {
      // loaded into cache, do X
    } else {
      // do Y
    }
  }
}

final class Cache {
   private static final ConcurrentHashMap<String, List<String>> CACHE = new ConcurrentHashMap<>();

   static List<String> byName(String name) {
      final LoadingAction<String, List<String>> loader = new LoadingAction<>();
      final List<String> result = CACHE.computeIfAbsent(name, loader::load);

      loader.executePostLoad();

      return result;
   }

}
Run Code Online (Sandbox Code Playgroud)

或者把它翻过来:

class Loader<K, V> {
  private boolean called = false;

  public V load(final Map<K, V> map, final K key) {
    final V result = map.computeIfAbsent(key, this::load);
    this.executePostLoad();
    return result;
  }

  private V load(final K key) {
    called = true;
    // load data
    return ...;
  }

  private void executePostLoad() {
    if (called) {
      // loaded into cache, do X
    } else {
      // do Y
    }
  }
}

final class Cache {
   private static final ConcurrentHashMap<String, List<String>> CACHE = new ConcurrentHashMap<>();

   static List<String> byName(String name) {
      final Loader<String, List<String>> loader = new Loader<>();
      return loader.load(CACHE, name);
   }

}
Run Code Online (Sandbox Code Playgroud)

构造和加载可以封装在静态方法中:

class Loader<K, V> {
  private boolean called = false;

  public static <K, V> V load(final Map<K, V> map, final K key) {
      final Loader<K, V> loader = new Loader<>();
      return loader.doLoad(map, key);
  }

  private V doLoad(final Map<K, V> map, final K key) {
    final V result = map.computeIfAbsent(key, this::load);
    this.executePostLoad();
    return result;
  }

  private V load(final K key) {
    called = true;
    // load data
    return ...;
  }

  private void executePostLoad() {
    if (called) {
      // loaded into cache, do X
    } else {
      // do Y
    }
  }
}

final class Cache {
   private static final ConcurrentHashMap<String, List<String>> CACHE = new ConcurrentHashMap<>();

   static List<String> byName(String name) {
      return Loader.load(CACHE, name);
   }

}
Run Code Online (Sandbox Code Playgroud)

  • X 只会执行一次,但在执行时,其他线程已经可以从映射中检索“V”并执行 Y,甚至在 X 仍在执行时将“V”返回给调用者。我之前评论中的“多个”一词仅指Y,而如果没有括号短语,则表示“X和Y”,意味着可以同时运行X和Y。 (2认同)