On Jun 14, 2013, at 11:57 PM, Mike Duigou <mike.dui...@oracle.com> wrote: > I have updated the webrev again. In addition to moving the modification > checks to be consistently after the operation for the most complete > "fast-fail" behaviour I've also slightly enhanced the Map default to detect > ISE thrown by setValue as a CME condition. > > http://cr.openjdk.java.net/~mduigou/JDK-8016446/2/webrev/ >
There are some places where the indentation looks wonky e.g. HashMap & LinkedHashMap changes. The LinkedHashMap.forEach/replaceAll methods are checking modCount and throwing CME before the action is called. The WeakHashMap.replaceAll method is checking the modCount per bucket, not-per element. Are there existing tests for the overriding methods? Otherwise looks OK. Paul. > For interference detection the strategy I am advocating is : > > - serial non-stream operations > (Collection.forEach/Iterator.forEachRemaining): per-element post-operation > ("fast-fail", "best-efforts"). As Remi has pointed out the failure modes for > collection.forEach() and for(:collection) will differ and it is a conscious > choice to provide superior fast-fail than to match behaviour. > > - stream terminal operations, both serial and parallel : at completion if at > all. Having the serial and parallel stream interference detection behaviours > consistent is prioritized over making serial stream behaviour match behaviour > of non-stream iteration methods. > > Mike > > On Jun 12 2013, at 22:28 , Mike Duigou wrote: > >> I have updated my webrev with Remi's improvements and some other >> improvements to the fast-fail concurrent modification checking. >> >> Revised webrev: >> >> http://cr.openjdk.java.net/~mduigou/JDK-8016446/1/webrev/ >> >> Mike >> >> >> On Jun 12 2013, at 15:49 , Remi Forax wrote: >> >>> On 06/12/2013 11:23 PM, Mike Duigou wrote: >>>> Hello all; >>>> >>>> This patch adds optimized implementations of Map.forEach and >>>> Map.replaceAll to HashMap, Hashtable, IdentityHashMap, WeakHashMap, TreeMap >>>> >>>> http://cr.openjdk.java.net/~mduigou/JDK-8016446/0/webrev/ >>>> >>>> The implementations traverse the map more efficiently than the default >>>> iterator implementation and sometimes avoid creation of transient >>>> Map.Entry instances. The fast-fail behaviour of the default implementation >>>> is retained. >>>> >>>> Mike >>> >>> Hi Mike, >>> funnily I was writing HashMap.forEach/LinkedHashMap.forEach at the same >>> time. >>> (you need also to override forEach in LinkedHashMap because the one you >>> inherits from HashMap doesn't use the linked list of entries). >>> >>> My code is slightly different from yours because I've moved the cases where >>> the item is a red/black tree out of the fast path >>> (the tree is created either if you are unlucky, if hashCode is badly >>> written or if someone forge keys to have collisions) >>> and does the modCount check for each element because a call to the consumer >>> can change the underlying map >>> so you can not do a modCount check only at the end. >>> >>> Rémi >>> >>> Here is the diff. >>> diff -r 6df79b7bae6f src/share/classes/java/util/HashMap.java >>> --- a/src/share/classes/java/util/HashMap.java Wed Jun 12 09:44:34 2013 >>> +0100 >>> +++ b/src/share/classes/java/util/HashMap.java Thu Jun 13 00:46:05 2013 >>> +0200 >>> @@ -28,6 +28,7 @@ >>> import java.io.*; >>> import java.lang.reflect.ParameterizedType; >>> import java.lang.reflect.Type; >>> +import java.util.function.BiConsumer; >>> import java.util.function.Consumer; >>> import java.util.function.BiFunction; >>> import java.util.function.Function; >>> @@ -2615,6 +2616,54 @@ >>> int capacity() { return table.length; } >>> float loadFactor() { return loadFactor; } >>> >>> + >>> + @Override >>> + public void forEach(BiConsumer<? super K, ? super V> action) { >>> + Objects.requireNonNull(action); >>> + final int expectedModCount = modCount; >>> + if (nullKeyEntry != null) { >>> + forEachNullKey(expectedModCount, action); >>> + } >>> + Object[] table = this.table; >>> + for(int index = 0; index < table.length; index++) { >>> + Object item = table[index]; >>> + if (item == null) { >>> + continue; >>> + } >>> + if (item instanceof HashMap.TreeBin) { >>> + eachTreeNode(expectedModCount, ((TreeBin)item).first, >>> action); >>> + continue; >>> + } >>> + @SuppressWarnings("unchecked") >>> + Entry<K,V> entry = (Entry<K,V>)item; >>> + for (;entry != null; entry = (Entry<K,V>)entry.next) { >>> + if (expectedModCount != modCount) { >>> + throw new ConcurrentModificationException(); >>> + } >>> + action.accept(entry.key, entry.value); >>> + } >>> + } >>> + } >>> + >>> + private void eachTreeNode(int expectedModCount, TreeNode<K,V> node, >>> BiConsumer<? super K, ? super V> action) { >>> + while (node != null) { >>> + if (expectedModCount != modCount) { >>> + throw new ConcurrentModificationException(); >>> + } >>> + @SuppressWarnings("unchecked") >>> + Entry<K,V> entry = (Entry<K,V>)node.entry; >>> + action.accept(entry.key, entry.value); >>> + node = (TreeNode<K,V>)entry.next; >>> + } >>> + } >>> + >>> + private void forEachNullKey(int expectedModCount, BiConsumer<? super >>> K, ? super V> action) { >>> + if (expectedModCount != modCount) { >>> + throw new ConcurrentModificationException(); >>> + } >>> + action.accept(null, nullKeyEntry.value); >>> + } >>> + >>> /** >>> * Standin until HM overhaul; based loosely on Weak and Identity HM. >>> */ >>> diff -r 6df79b7bae6f src/share/classes/java/util/LinkedHashMap.java >>> --- a/src/share/classes/java/util/LinkedHashMap.java Wed Jun 12 09:44:34 >>> 2013 +0100 >>> +++ b/src/share/classes/java/util/LinkedHashMap.java Thu Jun 13 00:46:05 >>> 2013 +0200 >>> @@ -24,7 +24,8 @@ >>> */ >>> >>> package java.util; >>> -import java.io.*; >>> + >>> +import java.util.function.BiConsumer; >>> >>> /** >>> * <p>Hash table and linked list implementation of the <tt>Map</tt> >>> interface, >>> @@ -470,4 +471,16 @@ >>> protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { >>> return false; >>> } >>> + >>> + @Override >>> + public void forEach(BiConsumer<? super K, ? super V> action) { >>> + Objects.requireNonNull(action); >>> + int expectedModCount = modCount; >>> + for (Entry<K,V> entry = header.after; entry != header; entry = >>> entry.after) { >>> + if (expectedModCount != modCount) { >>> + throw new ConcurrentModificationException(); >>> + } >>> + action.accept(entry.key, entry.value); >>> + } >>> + } >>> } >>> >>> >> >