javaconcurrencycopyonwritearraylist

Can using foreach of CopyOnWriteArrayList cause ConcurrentModificationException in java?


I look to java 11 implementation of .foreach method in CopyOnWriteArrayList

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    for (Object x : getArray()) {
        @SuppressWarnings("unchecked") E e = (E) x;
        action.accept(e);
    }
}

I see that it just loops the array without any locks. Can add() or remove() performed concurrently with foreach give a ConcurrentModificationException? In contrast to iterator(), foreach seems to avoid using the copy of original array on write and it uses no locks.


Solution

  • Can using foreach of CopyOnWriteArrayList cause ConcurrentModificationException in java?

    No. You can see that from the code that it doesn't throw ConcurrentModificationException:

    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        for (Object x : getArray()) {
            @SuppressWarnings("unchecked") E e = (E) x;
            action.accept(e);
        }
    }
    

    Note that getArray() call is not copying the array. It is declared like this:

    private transient volatile Object[] array;
    
    final Object[] getArray() {
        return array;
    }
    

    (Since array is volatile, no lock is needed to ensure that getArray() returns the current version of the array.)


    Can add() or remove() performed concurrently with foreach give a ConcurrentModificationException?

    A call to those methods will cause a new backing array to be created with the update. This is done holding a lock on the CopyOnWriteArrayList, and then the array is replaced.

    Meanwhile, the foreach() call will loop over the old array as if nothing happened.


    In contrast to iterator(), foreach seems to avoid using the copy of original array on write and it uses no locks.

    Actually, iterator() behaves the same way as foreach. It calls getArray() to get the current backing array.

    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    

    And if you look at the COWIterator class, it doesn't throw ConcurrentModificationException either.


    Note that this is all specified in the javadocs.

    1. The javadocs for CopyOnWriteArrayList state:

      "... the iterator is guaranteed not to throw ConcurrentModificationException."

    2. The javadocs for foreach (in Iterable) state:

      "The default implementation behaves as if:"

      for (T t : this) action.accept(t);

      which is using the iterator provided by CopyOnWriteArrayList that doesn't throw ConcurrentModificationException; see 1.


    However, there is a small gotcha. A sublist of a CopyOnWriteArrayList is not a CopyOnWriteArrayList, and it can produce a ConcurrentModificationException; see CopyOnWriteArrayList throwing CurrentModificationException