Difference between Concurrent HashMap merge() and put()

Viewed 368

I recently tumbled upon an article which stated the importance of merge method in ConcurrentHashMap performing atomic operations. Below is the link for the article:

https://www.nurkiewicz.com/2019/03/mapmerge-one-method-to-rule-them-all.html

It says that merge method either puts new value under the given key (if absent) or updates existing key with a given value (UPSERT) with explanation of this concept. However, this is exactly what put() method does.

The get() and then put() together in ConcurrentHashMap are not thread-safe.

Please guide me to understand how is merge() taking care of this get() and put() together scenario and making operations on ConcurrentHashMap thread-safe?

2 Answers

One key concept of concurrent code is that two atomic operations execute one after the other don't necessarily have the same effect as a single atomic operation doing the same thing.

Let's ignore the case where the map doesn't already contain a value, since that is the less interesting one (there's still a race condition that you'd need to handle, but the other one is more interesting).

So if you already have a value for a given key, then merge basically becomes put(key, mergeFunction(newValue, get(key)).

If you implemented it that way then you could run into a very real issue of losing updates:

  1. your thread executes get(key) and gets the current value (let's call it v1)
  2. another thread updates the binding for key to a new value (let's call it v2)
  3. your thread computes the new updated value using v1 and your argument to merge (let's call it v3)
  4. your thread updates the binding for key to the newly merged value v3

Note that the update to v2 is basically ignored (overwritten) by your code. If the map values are meant to represent counters, that means that you've basically ignored one update entirely and will get a wrong result.

What merge provides is a way to apply an update to an existing value without having to worry about other concurrent updates changing the value out from under you and you losing updates.

The point you are missing is that the merge method accepts a BiFunction (a remappingFunction) which compute the new value whereas a put blindly inserts/replaces a value.

The remappingFunction received the old and new values and using them you can perform some computation and return the new updated value.

Related