Search code examples
javamultithreadingconcurrencyjava.util.concurrent

Atomicity of combined putIfAbsent and replace with ConcurrentMap


This refers to an accepted solution to the a question raised in this thread

public void insertOrReplace(String key, String value) {
    for (;;) {
        String oldValue = concurrentMap.putIfAbsent(key, value);
        if (oldValue == null)
            return;

        final String newValue = recalculateNewValue(oldValue, value);
        if (concurrentMap.replace(key, oldValue, newValue))
            return;
    }
}

I would love to thoroughly understand the code.

I believe putIfAbsent and replace altogether can be considered as a compound operation. Is this compound operation atomic without explicit synchronization on object oldValue? Or just because of the for loop, it guarantees the atomicity? What exactly does the loop do? Would it cause an infinite loop that makes the method never end?


Solution

  • First of all, the original question introduces somewhat bizarre code. Not sure what it is good for but lets leave that aside.

    Second thing, the chosen answer and the one that you copy-pasted here is just a different way to do the same code as in the question.

    So, what does this (bizarre) method do:

    1. Checks if the key is mapped to a value and if not assigns the value and exists.
    2. If it was, recalculate the value and replace the old value.

    Why do we need the loop then? Cause if 2 threads are recalculating the value and do the replace "together", only 1 is going to be successful and hence return from the method. The "loosing" thread will need to go thru the process again. Probably will get a new value in the putIfAbsent call and will recalculate a new value to be replaced, this time might be OK.

    A few things to note:

    1. I would recommend reading carefully the APIs of ConcurrentMap's putIfAbsent and replace methods.
    2. There is a corner case (that will never happen in reality) that a call to this method will be stuck forever cause it will always "loose" to another call.
    3. I suspect that synchronizing this whole method will be better avoiding this whole cumbersome loopings. But since I am not fully aware of what this code aims to resolve I can not officially claim that.