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

时间:2015-04-23 14:40:31

标签: java concurrency thread-safety atomic compare-and-swap

我需要写一个非常简单的字典,它只会附加。字典将在许多线程之间共享。当任何线程调用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();
    }  
}

1 个答案:

答案 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>读操作之前,一个线程写入的结果才能保证对另一个线程的读取可见。

语言规范说:

  

可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并且在第二个之前被命令。

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