3

I want to understand internally how concurrent modification exception is handled in concurrent collections like ConcurrentHashMap and CopyOnWriteArrayList.

There are so many blogs available in internet which suggest to use these two data structures to avoid concurrent modification exception. But nothing explains , how this exception is internally handled by concurrent collection.

Can someone give more insights on this? I need some detailed explanation.

JavaUser
  • 25,542
  • 46
  • 113
  • 139
  • 1
    Note that the most common source of `ConcurrentModificationException` exceptions is *not* concurrency! The most common situation is where you are iterating over and modifying a collection at the same time *within a single thread*. – Daniel Pryden Apr 22 '19 at 13:06
  • There are currently 514 questions tagged [tag:concurrentmodification]. Are you sure one of those doesn't already answer your question? – Daniel Pryden Apr 22 '19 at 13:11
  • Yes . Thats why I raised this question. There is no single page reference – JavaUser Apr 22 '19 at 13:13
  • That's not a good reason to create a new question, though. What we normally do is to pick an existing question and improve the answers. If the answers aren't good enough, you can offer a bounty on the question or add a new answer. – Daniel Pryden Apr 22 '19 at 13:14
  • @DanielPryden the OP question is why CopyOnWriteArrayList and ConcurrentHashMap prevent the CME from being thrown. Your linked answer doesn't address that question. I would imagine an answer does exist though. – John Vint Apr 22 '19 at 13:29
  • @JohnVint: From JavaUser's comment about looking for a "single page reference" I don't think the intent of the question is to understand the technical reasons why `CopyOnWriteArrayList` and `ConcurrentHashMap` *don't* raise `ConcurrentModificationException`: indeed, `ArrayList` and `HashMap` **need to go out of their way to throw it**! You could easily have a version of `ArrayList` that doesn't throw CME by simply removing all the code that throws it. But that would be **wrong** because CME isn't a problem to avoid, it's a safeguard to tell you that there is **already** a bug somewhere else. – Daniel Pryden Apr 22 '19 at 13:34
  • You may be right that his intention was a one-stop-shop for answers (which isn't possible). I am only addressing what the question is asking. If there is a better answer that does answer this technical question, then I am all for it. – John Vint Apr 22 '19 at 13:45

3 Answers3

4

The literal answer to your question is not very interesting. ConcurrentHashMap and CopyOnWriteArrayList don't throw ConcurrentModificationException because they don't include code to throw it.

It's not like ConcurrentModificationException is some low-level intrinsic thing. ArrayList and HashMap, among other collection classes, throw ConcurrentModificationException to help you. They have to include extra code to try to detect concurrent modifications, and extra code to throw an exception. ConcurrentModificationException is thrown when one of those classes detect that there is a bug somewhere that is causing an unsafe modification to your collection.

Classes that support safe concurrent modification don't throw ConcurrentModificationException because they don't need to.

If you're trying to debug a ConcurrentModificationException, there are plenty of other questions that help answer that:

Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135
1

Here is the add() method definition of ArrayList and CopyOnWriteArrayList.

ArrayList:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

CopyOnWriteArrayList:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

From the above code, it is clear that CopyOnWriteArrayList takes lock before modifying the map. Here I have just posted the code of the add method. If you look on the code of remove() / addAll() or any method which modifies the List structurally you can see that it takes lock before modifying the collection. Also ArrayList's iterator's method such as next()/remove() check for modification but for CopyOnWriteArrayList's iterator's method does not check for the modification. For example :

ArrayList iterator next() method:

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

CopyOnWriteArrayList iterator next() method:

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
Amit Bera
  • 7,075
  • 1
  • 19
  • 42
  • how modification count is handled in this scenario? . Is there any specific change on modCount implementation? – JavaUser Apr 22 '19 at 13:08
  • 1
    Neither of these code snippets show how `ConcurrentModificationException` is avoided. In the case of `CopyOnWriteArrayList`, it's because the internal `Iterator` implementation keeps a reference to the *same* underlying `elements` array throughout the course of iterating, so it doesn't "see" any modifications after that point. – Daniel Pryden Apr 22 '19 at 13:08
  • 1
    @JavaUser: `modCount` is how `ArrayList` detects unexpected concurrent modifications, which causes it to raise `ConcurrentModificationException`. As the Javadoc says: "Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs." – Daniel Pryden Apr 22 '19 at 13:09
  • @ Daniel , can you please consolidate and write your views by answer ? . This will helps the readers and get the complete picture. – JavaUser Apr 22 '19 at 13:11
  • 1
    @JavaUser I have edited my answer, please take a look. It may help you. Thanks. – Amit Bera Apr 22 '19 at 13:44
1

This will, right now, answer how CopyOnWriteArrayList avoids the need for a ConcurrentModificationException.

When you modify the collection the CopyOnWriteArrayList does two things

  1. It prevents other threads from modifying the collection via locking
  2. Copies all the elements in the current CopyOnWriteArrayList into a new array and then assigns that new array to the class's array instance

So how does that prevent a CME? A CME in standard collections will only be thrown as a result of iterating. The exception gets thrown if, while iterating over the collection, an add or remove is executed on the same collection instance.

The CopyOnWriteArrayList's iterator assigns the current array as a final field snapshot of the collection and uses that for iteration. If another thread (or even the same thread) tries to add to the CopyOnWriteArrayList then updates will be applied to a new copy and not the snapshot one we are currently iterating.

For instance, we know the add method looks like

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

Notice the thread local newElements assignment being made, when that is completed it will set to the class instance volatile array.

Then comes the iterator, it's defined as

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

So when iterating, we are reading whatever was the array prior to any modifications, and since no other thread can modify the snapshot we are looking at a ConcurrentModificationException cannot happen.

John Vint
  • 39,695
  • 7
  • 78
  • 108