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

I need to write a really simple dictionary that will only be added. The dictionary will be shared among many threads. When any thread calls getId

, I want to make sure the same id is always returned for the same word, i.e. There should be only one id for any unique word.

Now, obviously, I could just sync access to the method getId

, but that's not much fun. So I wondered if there is a blocking way for this.

In particular, I am wondering about thread safety when using java.util.concurrent.ConcurrentHashMap#computeIfAbsent

. javadoc for frontend ConcurrentMap

says:

The default implementation can repeat these steps when multiple threads try to perform updates, including potentially calling the mapping function multiple times.

It is not clear to me from this description, does this mean that the mapping function can be called more than once for the same key ?

If this is the case (i.e. the same key could be called more than once), then I think the following code is most likely not thread-safe, since it could be called getAndIncrement

more than once for the same ( i.e. word).

If it is not, 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();
    }  
}

      

+3


source to share


1 answer


This is guaranteed to be thread-safe with the ConcurrentMap

Javadoc (focus):

The actions on a thread before placing an object in ConcurrentMap as a key or value occurred before an action after accessing or deleting this object from ConcurrentMap on another thread.

The ConcurrentHashMap

Javadoc has an example similar to yours:

ConcurrentHashMap can be used as a scalable frequency map (histogram or multiset form) using LongAdder values ​​and initialization via computeIfAbsent. For example, to add a counter to ConcurrentHashMap<String,LongAdder> freqs

, you can usefreqs.computeIfAbsent(k -> new LongAdder()).increment();

While in use computeIfAbsent

, it should be similar putIfAbsent

.



The java.util.concurrent

Javadoc package talks about "happens first":

Chapter 17 of the Java Language Specification defines an incident-to relationship in memory operations such as reading and writing shared variables. Write results on one thread are guaranteed to be read by another thread only if the write operation occurs before the read operation.

And the language description says:

Two actions can be ordered using a happen-before relationship. If one action takes place in front of another, then the first is visible and ordered before the second.

Thus, your code must be thread safe.

+2


source







All Articles