我需要写一个非常简单的字典,它只会附加。字典将在许多线程之间共享。当任何线程调用getId
时,我想确保始终为同一个单词返回相同的id,即任何唯一单词只应该有一个id。
现在显然我可以同步访问getId
方法,但这不是很有趣。所以我想知道是否有一种无锁的方法来实现这一点。
特别是,我想知道使用java.util.concurrent.ConcurrentHashMap#computeIfAbsent
的线程安全性。接口ConcurrentMap
的{{3}}表示:
当多个线程尝试更新(包括可能多次调用映射函数)时,默认实现可能会重试这些步骤。
根据该描述,我不清楚这是否意味着相同的键可以多次调用映射函数 ?
如果是这种情况(即对于相同的密钥可能会多次调用映射器),那么我认为以下代码很可能不是线程安全的,因为它可以多次调用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();
}
}
答案 0 :(得分:2)
ConcurrentMap
Javadoc(强调我的)保证线程安全:
在将对象放入ConcurrentMap之前的一个线程中的操作,作为另一个线程中来自ConcurrentMap的访问或删除的访问或删除之后的键happen-before操作。 / p>
ConcurrentHashMap
Javadoc有一个类似于你的例子:
ConcurrentHashMap可以通过使用LongAdder值并通过computeIfAbsent初始化,用作可伸缩频率图(直方图或多重集的一种形式)。例如,要将计数添加到
ConcurrentHashMap<String,LongAdder> freqs
,您可以使用freqs.computeIfAbsent(k -> new LongAdder()).increment();
虽然它使用computeIfAbsent
,但它应该类似于putIfAbsent
。
java.util.concurrent
包Javadoc谈到“之前发生”:
Chapter 17 of the Java Language Specification定义了内存操作上的发生之前关系,例如共享变量的读写。只有当写操作发生在_ / em>读操作之前,一个线程写入的结果才能保证对另一个线程的读取可见。
语言规范说:
可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并且在第二个之前被命令。
所以你的代码应该是线程安全的。