Hi, I think we should as consistent as possible about the functions being side-effect free when applied to "bulk" operations. A method such as computeIfAbsent can be viewed as a bulk operation in that it may perform two or more dependent actions (they are just not as bulky as forEach).
It's inconsistent if we state the functions should be side-effect free *but* map implementations tolerate side-effects resulting in state changes for entries other than that associated with the key under operation. I am not even sure this can be easily guaranteed with CHM in the face of resizes and keys hashing to the same bucket. So i propose: - the functions should be side-effect free. - non-concurrent map implementations should, on a best-effort basis, detect comodification and fail with CME. - concurrent map implementations should, on a best-effort basis, detect non-termination situations and fail with ISE. - document the best-effort behaviour and advise that implementations should override the default implementations if they want to do better. Alas we cannot do anything about the default method implementations, but i don't think we should be constraining general behaviour based on that exact implementations (just as we do not for concurrent maps, it "behaves as if"). Paul. On Feb 4, 2015, at 4:01 AM, Stuart Marks <stuart.ma...@oracle.com> wrote: > On 2/3/15 4:01 PM, Brent Christian wrote: >> The code in bug 8071667 [1] passes a mappingFunction to computeIfAbsent() >> which >> itself put()s a sufficient number of additional entries into the HashMap to >> cause a resize/rehash. As a result, computeIfAbsent() doesn't add the new >> entry >> at the proper place in the HashMap. >> >> While one should not (mis)use the mappingFunction in this way, >> HashMap.computeIfAbsent() (and similar HashMap methods which accept Lambda >> expressions) could check for and throw a ConcurrentModificationException on a >> "best-effort" basis, similar to iterators. This is already done in bulk >> operations HashMap.forEach() and HashMap.replaceAll(). >> >> I think it's also worth making mention of this in the JavaDoc. > > I think we need to have the specification discussion *first* before we decide > what HashMap should do with side-effecty computations that are passed to > computeIfAbsent and friends. Unfortunately the API specification for > Map.computeIfAbsent is largely silent about what should happen. I don't know > whether that means that the result should be undefined, or that passing a > function with side effects is implicitly allowed and should therefore be > defined. > > I'd think it would be quite unpleasantly surprising to find that passing a > mapping function with side effects -- especially on keys other than the > current operation -- results in essentially a corrupted map. Then again, I'm > surprised that somebody would think to pass a mapping function that does have > side effects. However, this is what people do, and they expect the library to > behave reasonably. > > I can think of an (only moderately contrived) use case that's probably behind > the bug report. Suppose I want to have a map that starts empty but which is > lazily initialized, and when it's initialized, it should contain entries for > all keys A, B, C, D, and E. Furthermore, it should be lazily initialized when > any one of these keys is put into the map. Of course, you can write this out > easily yourself. But hey, there's this new computeIfAbsent() method that > should let me do > > map.computeIfAbsent(key, k -> { > /* put all entries A..E except k */ > return value_for_k; > }); > > Based on the @implSpec for Map.computeIfAbsent, I'd expect this to work. And > if your map inherits the Map.computeIfAbsent default implementation, it > probably does work. Indeed, the workaround given in the bug report is > essentially to implement your own method that duplicates the logic of the > @implSpec and the default method. So, I'm leaning toward specifying that side > effects should be supported, and that ConcurrentModificationException should > not be thrown. > > That implies that HashMap will have to detect the concurrent modification and > deal with it instead of throwing an exception. > > If we do clarify the spec to support this case, it probably shouldn't make > any guarantees about what should happen if the mapping function puts the > *same* key. That is, if > > map.computeIfAbsent(key, k -> { > put(k, value1); > return value2; > }); > > it seems reasonable that it not be defined which of value1 or value2 ends up > getting mapped to the key. > > s'marks > > >> Here's an example of what might be done, using computeIfAbsent() as an >> example: >> >> http://cr.openjdk.java.net/~bchristi/8071667/webrev.0/ >> >> I would update HashMap and Hashtable. It looks like >> ConcurrentHashMap.computeIfAbsent() already forbids such usage, stating that >> the >> computation "must not attempt to update any other mappings of this map." >> >> >> Comments on this approach? >> >> Thanks, >> -Brent >> >> 1. https://bugs.openjdk.java.net/browse/JDK-8071667 >> "HashMap.computeIfAbsent() adds entry that HashMap.get() does not find."