这个字典函数是线程安全的(ConcurrentHashMap+AtomicInteger)吗?

Is this dictionary function thread-safe (ConcurrentHashMap+AtomicInteger)?

我需要写一个非常简单的字典,它只能追加。字典将在多个线程之间共享。当任何线程调用时 getId 我想确保始终为同一个词返回相同的 ID,即任何唯一的词应该只有一个 ID。

现在显然我可以同步访问 getId 方法,但这不是很有趣。所以想知道有没有无锁的方法可以实现。

特别是,我想知道使用 java.util.concurrent.ConcurrentHashMap#computeIfAbsent 的线程安全性。接口 ConcurrentMapjavadoc 表示:

The default implementation may retry these steps when multiple threads attempt updates including potentially calling the mapping function multiple times.

根据该描述,我不清楚这是否意味着映射函数 可能 被多次调用 相同键?

如果是这种情况(即映射器可能会为同一个键调用多次),那么我认为以下代码很可能不是线程安全的,因为它可以多次调用 getAndIncrement一次用于相同的键(即单词)。

如果不是这样,那么我认为下面的代码是线程安全的。谁能确认一下?

public class Dictionary {
    private final AtomicInteger index = new AtomicInteger();
    private final ConcurrentHashMap<String, Integer> words =
             new ConcurrentHashMap<>();

    public int getId(final String word) {
        return words.computeIfAbsent(word, this::newId);
    }

    private int newId(final String word) {
        return index.getAndIncrement();
    }  
}

ConcurrentMap Javadoc(强调我的)保证线程安全:

Actions in a thread prior to placing an object into a ConcurrentMap as a key or value happen-before actions subsequent to the access or removal of that object from the ConcurrentMap in another thread.

ConcurrentHashMap Javadoc 有一个与你的相似的例子:

A ConcurrentHashMap can be used as scalable frequency map (a form of histogram or multiset) by using LongAdder values and initializing via computeIfAbsent. For example, to add a count to a ConcurrentHashMap<String,LongAdder> freqs, you can use freqs.computeIfAbsent(k -> new LongAdder()).increment();

虽然这使用了 computeIfAbsent,但它应该类似于 putIfAbsent

java.util.concurrent 包 Javadoc 谈论 "happens-before":

Chapter 17 of the Java Language Specification defines the happens-before relation on memory operations such as reads and writes of shared variables. The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation.

语言规范说:

Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

所以你的代码应该是线程安全的。