简体   繁体   中英

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

I need to write a really simple dictionary which will be append only. The dictionary will be shared between many threads. When any thread calls getId I want to make sure the same id is always returned for the same word, ie there should be only one id for any unique word.

Now obviously I could just synchronize access to the getId method, but that is not very fun. So I wondered if there was a lock-free way to achieve this.

In particular, I am wondering about the thread safety of using java.util.concurrent.ConcurrentHashMap#computeIfAbsent . The javadoc for the interface ConcurrentMap says:

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

From that description, it is not clear to me if that means that the mapping function might be called more than once for the same key?

If that is the case (ie the mapper might be called more than once for the same key), then I think the following code is most likely not thread-safe as it could call getAndIncrement more than once for the same key (ie word).

If that is not the case, then I think the following code is thread-safe. Can anyone confirm?

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();
    }  
}

This is guaranteed to be thread safe by the ConcurrentMap Javadoc (emphasis mine):

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.

The ConcurrentHashMap Javadoc has an example similar to yours:

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();

While this uses computeIfAbsent , it should be analogous to putIfAbsent .

The java.util.concurrent package Javadoc talks about "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.

And the Language Specification says:

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.

So your code should be thread safe.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM