0

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.

Vitalii
  • 10,091
  • 18
  • 83
  • 151

1 Answers1

3

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

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 1
    Good point about the gotcha - and the point goes even more generally, as according to the Javadoc for List.subList(), this kind of behaviour is "semantically undefined" :) Another gotcha to be aware of is that calls to get() on the sublist will not be lock-free, whereas the raison d'être of CopyOnWriteArrayList is to provide lock-free reads. Put another way, if you have a use case for calling subList() on a CopyOnWriteArrayList, you should possibly be using a different flavour of list... – Neil Coffey May 22 '22 at 06:11