简体   繁体   中英

Updating both a ConcurrentHashMap and an AtomicInteger safely

I have to store words and their corresponding integer indices in a hash map. The hash map will be updated concurrently.

For example: lets say the wordList is {a,b,c,a,d,e,a,d,e,b} The the hash map will contain the following key-value pairs

a:1
b:2
c:3
d:4
e:5

The code for this is as follows:

public class Dictionary {

private ConcurrentMap<String, Integer>  wordToIndex;
private AtomicInteger                   maxIndex;

public Dictionary( int startFrom ) {
    wordToIndex = new ConcurrentHashMap<String, Integer>();
    this.maxIndex = new AtomicInteger(startFrom);
}


public void insertAndComputeIndices( List<String> words ) {

    Integer index;
    //iterate over the list of words
    for ( String word : words ) {
        // check if the word exists in the Map
        // if it does not exist, increment the maxIndex and put it in the
        // Map if it is still absent
        // set the maxIndex to the newly inserted index

        if (!wordToIndex.containsKey(word)) {
            index = maxIndex.incrementAndGet();

            index = wordToIndex.putIfAbsent(word, index);
            if (index != null)
                maxIndex.set(index);
        }
    }
}

My question is whether the above class is thread safe or not? Basically an atomic operation in this case should be to increment the maxIndex and then put the word in the hash map if it is absent.

Is there a better way to achieve concurrency in this situation?

No, it is not. If you have two methods A and B, both thread safe, this of course does not mean that calling A and B in a sequence is also thread safe, as a thread can interrupt another one between the function calls. This is what happens here:

    if (!wordToIndex.containsKey(word)) {
        index = maxIndex.incrementAndGet();

        index = wordToIndex.putIfAbsent(word, index);
        if (index != null)
            maxIndex.set(index);
    }

Thread A verifies that wordToIndex does not contain the word "dog" and proceeds inside the if. Before it can add the word "dog", thread B also finds that "dog" is not in the map (A did not add it yet) so it also proceeds inside the if. Now you have the word "dog" trying to be inserted twice.

Of course, putIfAbsent will guarantee that only one thread can add it, but I think that your goal is to not have two threads enter the if at the same time with the same key.

Clearly another thread can see maxIndex incrementing and then getting clobbered.

Assuming this is all that is going on to the map (in particular, no removes), then you could try putting the word in the map and only incrementing if that succeeds.

    Integer oldIndex = wordToIndex.putIfAbsent(word, -1);
    if (oldIndex == null) {
        wordToIndex.put(word, maxIndex.incrementAndGet());
    }

(Alternatively for a single put , use some sort of mutable type in place of Integer .)

AtomicInteger is something you should consider using.

And you should wrap all the code that needs to happen as a transaction in a synchronized(this) block.

The other answers are correct --- there are non-thread-safe fields in your class. What you should do, to start, is make sure

how to implement the threading

1) I would make sure everything internal is private, although this is not a requirement of thread-safe code.

2) Find any of your accessor methods, make sure they are snychronized whenever the state of the global object is modified (OR AT LEAST THE IF BLOCK IS SYNCHRONIZED).

3) Test for deadlocks or bad counts, this can be implemented in a unit test by making sure the value of maxIndex is correct after 10000 threaded inserts, for example...

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