1

This is follow up question to my original SO question.

Thanks to the answer on that question, it looks like that according to ConcurrentMap.computeIfPresent javadoc

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

My question is:

Does ConcurrentHashMap.computeIfPresent call remappingFunction multiple times when it is shared between multiple threads only or can also be called multiple times when created and passed from a single thread?

And if it is the latter case why would it be called multiple times instead of once?

tsolakp
  • 5,858
  • 1
  • 22
  • 28
  • Are you asking about the specification or the implementation? – shmosel Feb 05 '18 at 20:56
  • @shmosel. Specification. – tsolakp Feb 05 '18 at 20:56
  • Then "why" isn't necessarily relevant or answerable. – shmosel Feb 05 '18 at 20:57
  • @shmosel. Sorry not sure what you mean? – tsolakp Feb 05 '18 at 20:58
  • @tsolakp **unrelated** but reminded me about : `ConcurrentHashMap chm = new ConcurrentHashMap<>(); chm.put("one", 1); chm.computeIfAbsent("two", key -> { chm.computeIfAbsent("two", key2 -> { return 2; }); return 2; }); System.out.println(chm);` run this with java-8 and 9 for fun... – Eugene Feb 05 '18 at 21:25
  • @Eugene. Interesting in java8 it goes into infinite loop, but if I use different key in nested `computeIfAbsent` call it works as expected. – tsolakp Feb 05 '18 at 22:30
  • @tsolakp you can try that in different instances of `Map` in java-8 and java-9, some of them have been patched to work correctly in 9; but not all of them – Eugene Feb 06 '18 at 07:51
  • @Eugene you can simplify your example to `ConcurrentHashMap chm = new ConcurrentHashMap<>(); chm.computeIfAbsent("two", key -> chm.computeIfAbsent("two", key2 -> 2));` – Holger Feb 06 '18 at 17:19

3 Answers3

4

The general contract of the interface method ConcurrentMap.computeIfPresent allows implementations to repeat evaluations in the case of contention and that’s exactly what happens when a ConcurrentMap inherits the default method, as it would be impossible to provide atomicity atop the general ConcurrentMap interface in a default method.

However, the implementation class ConcurrentHashMap overrides this method and provides a guaranty in its documentation:

If the value for the specified key is present, attempts to compute a new mapping given the key and its current mapped value. The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this map.

emphasis mine

So, since your question asks for ConcurrentHashMap.computeIfPresent specifically, the answer is, its argument function will never get evaluated multiple times. This differs from, e.g. ConcurrentSkipListMap.computeIfPresent where the function may get evaluated multiple times.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I marked @John Bollinger answer as not accepted (sorry my mistake in the first place for causing this) and would leave this to the community to decide which one is correct. – tsolakp Feb 06 '18 at 17:29
  • @tsolakp both answers are correct, they just answer different questions. The problem I see, is that future readers might get confused because it’s hard to recognize what the actual question is. – Holger Feb 06 '18 at 17:34
  • Feel free to edit the question. My intent was about `remappingFunction` usage in `ConcurrentHashMap.computeIfPresent` per specification (not actual implementation). – tsolakp Feb 06 '18 at 17:51
  • This is the correct answer to the way the question is now worded. The other answers (from my understanding) are based on the original wording of the question and now make no sense. – John Calcote Jul 08 '19 at 18:24
3

Does ConcurrentMap.computeIfPresent call remappingFunction multiple times when it is shared between multiple threads or can be called multiple times when created and passed from a single thread?

The documentation does not specify, but the implication is that it is contention of multiple threads to modify the mapping of the same key (not necessarily all via computeIfPresent()) that might cause the remappingFunction to be run multiple times. I would anticipate that an implementation would check whether the value presented to the remapping function is still the one associated with the key before setting the remapping result as that key's new value. If not, it would try again, computing a new remapped value from the new current value.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Wonder why not block other threads from updating the value when `remappingFunction` is called. In Javadoc it actually says so: "The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress". – tsolakp Feb 05 '18 at 19:40
  • @tsolakp why would you want to block all threads when you could update separate mappings in separate buckets, without stepping on other thread toes? that's the entire point in CHM – Eugene Feb 05 '18 at 20:12
  • 1
    @tsolakp, I find that in the docs of `ConcurrentHashMap.computeIfPresent()`, but it is not documented on the `ConcurrentMap` interface, and `ConcurrentSkipListMap.computeIfPresent()` explicitly *disclaims* atomicity. – John Bollinger Feb 05 '18 at 20:15
  • 1
    @tsolakp, it is pretty common for Java's concurrent data structures to avoid locking. This allows implementations to use lower-level atomic operations instead, which are normally faster when in fact there is no conflicting access, and which allows a greater range of non-conflicting concurrent access. – John Bollinger Feb 05 '18 at 20:17
  • @John Bollinger. Does that mean that it is allowed for multiple call to `remappingFunction` in order to avoid locking? If we look at the source code we can see the use of `synchronized`. – tsolakp Feb 05 '18 at 20:37
  • 1
    @tsolakp, looking at the code can be illuminating, but it is the *documentation* on which you should rely. This is especially true for the methods of an interface such as `ConcurrentMap`, even where a default implementation is defined. With that said, yes, the idea behind allowing implementations of `ConcurrentMap.computeIfPresent()` to run the `remappingFunction` multiple times is to enable them to use low-level atomic operations instead of synchronization. That any particular implementation, such as `ConcurrentHashMap`'s, does not use that freedom is irrelevant. – John Bollinger Feb 05 '18 at 20:46
  • 1
    @tsolakp you are repeatedly mixing up the `ConcurrentMap` interface and the specific `ConcurrentHashMap` implementation. In the `default` implementation of `ConcurrentMap.computeIfPresent`, there is no `synchronized`. In the specific implementation of `ConcurrentHashMap.computeIfPresent`, you can find `synchronized` statements, but this method makes an explicit atomicity guaranty, so no, it won’t allow multiple evaluations to avoid locking, as that would contradict its specification. – Holger Feb 06 '18 at 10:08
  • @Holger. That was copy/paste error. The question is edited and asks only about `ConcurrentHashMap.computeIfPresent` specification. – tsolakp Feb 06 '18 at 15:10
  • 1
    @tsolakp but the question still cites the *interface* specification and you accepted an answer that only addresses the question as before the edit, i.e. the behavior mandated by the interface, not the specific behavior of the implementation of `ConcurrentHashMap.computeIfPresent`. Since [this specific method never evaluates the function more than once](https://stackoverflow.com/a/48640188/2711488), the question about when this may happen, is pointless. – Holger Feb 06 '18 at 15:23
  • @Holger the reason `ConcurrentMap` is mentioned is because only its Javadoc mentions usage of `remappingFunction` and javadoc for `ConcurrentHashMap.computeIfPresent` says that it is specified by javadoc of `ConcurrentMap.computeIfPresent`. My assumption is that whatever is specified for `ConcurrentMap.computeIfPresent` also applies to `ConcurrentHashMap.computeIfPresent`. As for accepted answer I commented about my mistake and edited the question before accepting one of the answers. – tsolakp Feb 06 '18 at 16:58
  • 1
    @tsolakp the interface method specifies that the method **may** evaluate the function multiple times when multiple threads attempt updates, not that a specific implementation does. And the specific implementation `ConcurrentHashMap.computeIfPresent` clearly states that it does the update atomically, blocking other updates, so it does not evaluate the function multiple times. Note that `Map.computeIfPresent` makes even weaker guarantees, but that doesn’t contradict the guarantees made by the more specific types. – Holger Feb 06 '18 at 17:10
  • @Holger. That was what I wanted to clarify. – tsolakp Feb 06 '18 at 17:17
2

you can see the code here:

@Override
default V computeIfPresent(K key,
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    V oldValue;
    while((oldValue = get(key)) != null) {
        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue != null) {
            if (replace(key, oldValue, newValue))
                return newValue;
        } else if (remove(key, oldValue))
           return null;
    }
    return oldValue;
}

if thread 1 comes in and calls the remappingFunction and gets the value X, then thread 2 comes and changes the value while thread 1 is waiting, and only then thread 1 calls "replace"...

then the "replace" method will return "false" due to the value change. so thread 1 will loop again and call the remappingFunction once again.

this can go on and on and create "infinite" invocations of the remappingFunction.

Imbar M.
  • 1,074
  • 1
  • 10
  • 19
  • Where did this code come from? The `ConcurrentHashMap.computeIfPresent` source code is different. – tsolakp Feb 05 '18 at 19:39
  • 1
    @tsolakp, I haven't checked, but I presume that's the default implementation of `ConcurrentMap.computeIfPresent()`. Your title notwithstanding, that's what the text of your question you asks about, *not* about `ConcurrentHashMap`'s implementation. – John Bollinger Feb 05 '18 at 20:26
  • @John Bollinger. My question was about `ConcurrentHashMap.computeIfPresen()` but the Javadoc comment was from `ConcurrentMap.computeIfPresent()`. The Javadoc for `ConcurrentHashMap.computeIfPresent()` does not say anything about how many times `remappingFunction` can be called. My assumption is that since `ConcurrentHashMap.computeIfPresent()` javadoc does not say otherwise it inherits same javadoc contract from `ConcurrentMap.computeIfPresent()`. – tsolakp Feb 05 '18 at 20:44
  • 1
    @tsolakp, the only place `ConcurrentHashMap` appears in your question at all is in the title. You reference the docs of the `ConcurrentMap` interface instead, and the actual question **also** asks about the interface: "Does *`ConcurrentMap.computeIfPresent`* call `remappingFunction` multiple times [...]?" (emphasis added). Moreover, your question makes sense only in that context, because there's no reason to think that `ConcurrentHashMap.computeIfPresent()` ever calls the `remappingFunction` more than once. – John Bollinger Feb 05 '18 at 20:50
  • @John Bollinger. You are right that was my mistake. Anyways I am going to give a point for this answer but accepting yours since that answers comes closer to my intended question. – tsolakp Feb 05 '18 at 20:52
  • @tsolakp it seems to me that you haven't accepted any answer. – Marko Bonaci Jan 22 '21 at 06:38