[concurrency-interest] ConcurrentHashMapV8

Doug Lea dl at cs.oswego.edu
Wed Oct 5 09:11:17 EDT 2011


I'm still contemplating changes in handling null-returns
in the new compute methods. But in the mean time, here
is another tiny corner-case issue that is handled differently
in ConcurrentHashMapV8 than in the existing j.u.c version.
I consider it just a misfeature-fix, but Joe Bowbeer remains
nervous about it, so urged me to post and solicit reaction.

This takes a fair amount of context to set up:

The Set<Map.Entry> you get from map.entrySet() has always
had some problematic specs, mainly because they are
based on those of Map.Entry. When you have a Map.Entry,
you don't know whether it is mutable vs immutable
because setValue is an optional operation. And in some
cases, you don't whether setValue actually updates
the map you got the entry from or just updates an
unbound Map.Entry object.

While it would be nice to better specify some of this,
the current Map-related specs are phrased in such a way
that implementations can normally do what users expect.
The main intent of setValue is to support only
one use case: iterating through entries one-by-one
and changing some of the mapped values in the absence
of any modifications by other threads. ConcurrentHashMap
(both the existing and V8 versions) supports this use case
in the expected way -- which of course can have surprising
effects if other threads ARE modifying the map while
you are doing this. But we don't go so far as
to disable the method because of this.

However, the write-through nature of setValue
(i.e., to actually change the mapped value in
the map) conflicts with the intent of the
entrySet.toArray() method. Even though the
Collection.toArray() spec doesn't explicitly
say so, users expect it to return an independent
copy of the elements, in array form. For example,
you would be surprised and unhappy if you did
   Object[] a = myArrayList.toArray();
   a[0] = 17;
and then found out that this caused myArrayList.get(0)
to also now return 17.

In the case of ConcurrentHashMap, this means that the
kind of Entry you get for the elements of entrySet.toArray
should NOT write back to the map you got them from.
In other words:
   Object[] a = myMap.entrySet().toArray();
   (Entry[])a[0].setValue(17);
should not change myMap.get(keyFor0) to return 17.

However, the existing j.u.c version does so. This was
changed to the safer non-write-through form for V8.
The original/existing behavior was mainly just due to
oversight -- it would have been different to begin with
if anyone had noticed this issue of establishing a
persistent aliasing path (unlike the transient path
available in one-by-one iteration).

In general the interactions of loose specs for both
Map.Entry and Collection.toArray mean that you can't
count on very much here, and it turns out that different
Maps do different things. For ConcurrentHashMap, we'd
like it to do the most sensible thing, at least in
future versions. However, because the specs are loose,
we cannot claim that this is a bug-fix; it is just
a mis-feature fix.

To me, fixing the mis-feature is more important
than retaining pure compatibility for a usage
that probably didn't do what people expected anyway.
But if you've ever done anything relying on the old j.u.c
behavior, could you let us know? Thanks!

-Doug


More information about the Concurrency-interest mailing list