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/

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);
>> +        }
>> +    }
>> }
>> 
>> 
> 

Reply via email to