[concurrency-interest] ConcurrentHashMapV8

Kasper Nielsen kasper at kav.dk
Mon Oct 3 13:13:57 EDT 2011


On 03-10-2011 15:52, Doug Lea wrote:
> On 10/03/11 09:34, √iktor Ҡlang wrote:
>
>> I'd love to be able to specify a Comparator in the constructor so that
>> conditional remove doesn't rely on reference-equality or .equals (since
>> oftentimes I cannot overload it)
>
> One reasonable way to do this interacts with a decision
> I keep flip-flopping on because people make good cases for
> all three of the possibilities: What should the map do when
> either a mapper or remapper function returns null?
> The possibilities are:
> 1. treat it as a user error, so throw NullPointerException
> 2. treat it as a (re)mapping failure, so ignore the outcome
> 3. treat it as a removal

I had a similar discussion with myself a while ago for a library I was 
designing. I ended up with 2. (ignoring the outcome) for a couple of reasons

1)
I allow users to recompute all mappings for a map. A.la
computeAll(BinaryMapper<? super K, ? super V, ? extends V> remapper)
which will run through any entry in the map and optionally compute a new 
value. Since I rarely need to update all values at the same time it is 
common I do not want to do anything with the value.

Although volatile writes are not that expensive. Having to update every 
mapping is still some task for large maps.

Of course, one way to do avoid this would be to see if the existing 
value is identical to the recomputed value. In which case the mapping 
does not need to be updated.

2)
In many situations there is a difference between a no-op and updating an 
entry in place.

If you working with something such as the event handlers defined in the 
upcoming JSR 107. You only want to notify these event handlers in case
a value changes.

Doing a simple check on identify by a map/cache as I suggested in 1). Is 
often not enough since users might not create a new value in the compute 
method, but instead update the value in place. For example, changing 
some bits in a blob.

Likewise, if you are working in a distributed setting you only want to 
send new values to other peers if they actually change.

3)
Although it is difficult to generalize. In case a Remapper returned a 
null as the result of a user error. I had the feeling a no-op is 
preferred instead of deleting the entry from the map.

4)
I wanted both computeIfPresent and computeIfAbsent to behave identical 
with respect to null being returned by a mapper/remapper. So either it 
was a user error (1) or it was a no-op (2). Which left 3. out.

Even if there are only few use case for it, returning null from 
computeIfAbsent will result in no mapping being established.

5)
Since Remapper/Mapper code is invoked within atomicity control, it is 
usually short and simple code. So few errors arise. At least I have yet 
to come across one resulting from returning null as a mistake.

6)
In general, I don't like null representing anything but a mistake. But 
in my case I had some use cases where the benefits of 2. (ignoring the 
outcome) outweighed my reluctance towards using nulls.

cheers
   Kasper


More information about the Concurrency-interest mailing list