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中会有漏洞。