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

Java 这个字典函数是线程安全的(ConcurrentHashMap+;AtomicInteger)吗?,java,concurrency,thread-safety,atomic,compare-and-swap,Java,Concurrency,Thread Safety,Atomic,Compare And Swap,我需要写一本非常简单的字典,它只会被附加。字典将在多个线程之间共享。当任何线程调用getId时,我希望确保总是为同一个单词返回相同的id,即,对于任何唯一的单词,应该只有一个id 现在很明显,我可以同步访问getId方法,但这不是很有趣。所以我想知道是否有一种无锁的方法来实现这一点 特别是,我想知道使用java.util.concurrent.ConcurrentHashMap#ComputeFabSent的线程安全性。接口ConcurrentMap的 当多个线程尝试更新(包括可能多次调用映射函

我需要写一本非常简单的字典,它只会被附加。字典将在多个线程之间共享。当任何线程调用
getId
时,我希望确保总是为同一个单词返回相同的id,即,对于任何唯一的单词,应该只有一个id

现在很明显,我可以同步访问
getId
方法,但这不是很有趣。所以我想知道是否有一种无锁的方法来实现这一点

特别是,我想知道使用
java.util.concurrent.ConcurrentHashMap#ComputeFabSent
的线程安全性。接口
ConcurrentMap


当多个线程尝试更新(包括可能多次调用映射函数)时,默认实现可能会重试这些步骤

从这个描述中,我不清楚这是否意味着映射函数可能会为相同的键调用多次

如果是这种情况(即,映射器可能会对同一个键调用多次),那么我认为以下代码很可能不是线程安全的,因为它可能会对同一个键(即word)多次调用
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();
    }  
}
公共类字典{
私有最终AtomicInteger索引=新的AtomicInteger();
私有最终ConcurrentHashMap字=
新的ConcurrentHashMap();
public int getId(最后一个字符串字){
返回words.computeIfAbsent(word,this::newId);
}
private int newId(最后一个字符串字){
返回index.getAndIncrement();
}  
}

这是由
ConcurrentMap
保证线程安全的(重点):

在将对象作为键或值放入ConcurrentMap之前的线程中的操作,在另一个线程中从ConcurrentMap访问或删除该对象之后的操作

ConcurrentHashMap
有一个与您类似的示例:

ConcurrentHashMap可以通过使用LongAdder值并通过ComputeFabSent初始化,用作可伸缩频率图(直方图或多重集的形式)。例如,要将计数添加到
ConcurrentHashMap freqs
,可以使用
freqs.computeIfAbsent(k->new LongAdder()).increment()

虽然它使用的是
computeifassent
,但它应该类似于
putIfAbsent

java.util.concurrent
包Javadoc谈到“之前发生”:

定义内存操作(如读取和写入共享变量)上的“发生在之前”关系。只有在写操作发生在读操作之前,一个线程的写操作的结果才能保证对另一个线程的读操作可见

语言规范说:

两个动作可以由“发生在之前”关系排序。如果一个动作发生在另一个动作之前,那么第一个动作对第二个动作可见并在第二个动作之前排序


因此,您的代码应该是线程安全的。

AFAICS这应该是线程安全的,但不能保证在分配的数字中没有间隔。@PeterLawrey您能解释一下数字中是如何存在间隔的吗?“默认实现可能会重试这些步骤”意味着多个线程尝试添加同一个单词存在风险,可以同时调用
newId
,但只有一个值将使用并返回。这会留下未使用的值。我刚刚重新检查了javadoc,而ConcurrentMap确实说“默认实现可能会重试这些步骤”。ConcurrentHashMap的javadoc似乎说映射函数将只调用一次-你认为呢?你可能是对的,但是我自己不相信。你同意@peterlawrey关于跳过某些ID的说法吗?@adamretter当
ConcurrentMap#compute*
方法说“可以重试”时,它指的是
默认的
实现没有被同步/原子化,但是,
ConcurrentHashMap
覆盖了所有这些
default
方法,并保证“整个方法调用是以原子方式执行的”,正如您所指出的。此外,如果重试这些方法的映射函数,那么
ConcurrentHashMap
Javadoc的“频率映射”示例将被破坏。因此,我不认为您的ID中会有漏洞。