最佳方式同步对具有可能更新整个地图的地图的读取和写入。

huangapple go评论74阅读模式
英文:

Best way to synchronize reads and writes on a map with potential updates on the entire map

问题

以下是代码的翻译部分:

// 定义了一个用于获取 JSON 配置的代码,这个 JSON 是一个字符串到布尔值的映射(它是一个保证的 JSON 模式)

// 我需要实现这个方法

private volatile Map<String, Boolean> flags = new HashMap<>();
private volatile String currFlagJson = "";
private final Retriever retriever; // 这个在其他地方初始化,但基本上返回一个缓存的 JSON,并使用单独的线程更新它。

public Optional<Boolean> isFlagEnabled(final String featureFlag) {
    final var json = retriever.retrieve();
    if (!json.equals(currFlagJson)) {
        updateMap(json);
        return Optional.ofNullable(flags.get(featureFlag));
    } else {
        return Optional.ofNullable(flags.get(featureFlag));
    }
}

private synchronized updateMap(final String newJson) {
    final var newMap = expensiveJsonParsingOperation(newJson);
    flags = newMap;
    currFlagJson = newJson;
}

我正在尝试避免多个线程同时更新这个映射,但我认为这并不完全有效。如果多个线程同时调用 equals 方法,而其中一个线程正在更新,那么最终所有线程都会尝试争夺更新它。当 expensiveJsonParsingOperation 正在进行时,我当然可以同步整个 isFlagEnabled,但这将是不必要的锁定,并且我可能正在提供读取请求。

我正在尝试找出如何最好地实现以下目标:

  1. 在需要时确保只有一个线程更新此映射。
  2. 不要在更新进行时阻塞读取线程。

也许可以考虑使用 ReentrantReadWriteLock 或其他适合此情况的东西。

英文:

So I have this fetcher code that fetches a json configuration which is a map of string to booleans (its a json schema that is guaranteed)

I have to implement this method

private volatile Map&lt;String, Boolean&gt; flags = new HashMap&lt;&gt;();
private volatile String currFlagJson = &quot;&quot;;
private final Retriever retriever; // This is initialized elsewhere, but it basically returns a cached json with a separate thread updating it. 

public Optional&lt;Boolean&gt; isFlagEnabled(final String featureFlag) {
   final var json = retriever.retrieve();
   if (!json.equals(currJson.get)) {
       updateMap(json);
       return Optional.ofNullable(flags.get(featureFlag));
   } else {
       return Optional.ofNullable(flags.get(featureFlag));
   }
}

private synchronized updateMap(final String newJson) {
    final var newMap = expensiveJsonParsingOperation(newJson)
    flags = newMap;
    currFlagJson = newJson;
}

I am trying to avoid multiple threads concurrently updating the map, but this I do not think completely works. If multiple threads race and calling the equals method, while one of the threads is updating, then eventually all the threads will try to contend updating it. I could of course synchronize the entirity of isFlagEnabled but that would be unnecessarily locking too and I could be serving read requests while the expensiveJsonParsingOperation is happening.

I am trying to figure out how best to

  1. Ensure exactly one thread updates this map when needed.
  2. Not block threads on reads while this update is happening.

Maybe. https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html? Or something else I can use for this case?

答案1

得分: 1

你可以使用双重检查锁定来确保只有在读者之间引入太多争用的情况下才更新一次:

public Optional<Boolean> isFlagEnabled(final String featureFlag) {
    var json = retriever.retrieve();
    if (!json.equals(currFlagJson)) {
        synchronized (this) {
            if (!json.equals(currFlagJson)) {
                flags = expensiveJsonParsingOperation(json);
                currFlagJson = json;
            }
        }
    }
    return Optional.ofNullable(flags.get(featureFlag));
}

请注意,如果只更新引用而不修改地图本身,则无需使用ConcurrentMap。如果使用不可变地图,还可以从flags中省略volatile(依赖currFlagJson的内存屏障来保持地图引用的新鲜)。否则,在另一个线程更新字段时,存在部分发布的风险。无论如何,currFlagJson必须是volatile以确保它不会被缓存。

英文:

You can use double-checked locking to make sure it only updates once without introducing too much contention between readers:

public Optional&lt;Boolean&gt; isFlagEnabled(final String featureFlag) {
    var json = retriever.retrieve();
    if (!json.equals(currFlagJson)) {
        synchronized (this) {
            if (!json.equals(currFlagJson)) {
                flags = expensiveJsonParsingOperation(newJson);
                currFlagJson = json;
            }
        }
    }
    return Optional.ofNullable(flags.get(featureFlag));
}

Note that there's no need to use a ConcurrentMap if you're only updating the reference without modifying the map itself. If you use an immutable map, you can also omit volatile from flags (relying on the currFlagJson memory barrier to keep the map reference fresh). Otherwise, there's a risk of partial publication if the field is read while another thread is updating it. Either way, currFlagJson must be volatile to ensure it's not cached.

huangapple
  • 本文由 发表于 2023年4月11日 01:19:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75979194.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定