19
votes

I'm occasionally getting a ConcurrentModificationException when I iterate over a list. A Google search informs me that it's probably because I'm altering that list in another thread while iterating over it and that to make this problem go away I should use java.util.concurrent.CopyOnWriteArrayList....

... except I already am.

Apparently, I'm doing something really stupid somewhere.

Does anybody have any insight into how one might induce CopyOnWriteArrayList to toss a ConcurrentModificationException? If it matters, I'm using Java 5.

Edit: Since the mutators I'm using may matter, I'm modifying this list in two ways:

  • Adding elements to the front. (list.add(0, newElement);)
  • Using subList to let older items fall off the back. (list = list.subList(0, MAX_LIST_SIZE);)

Do those raise red flags? If so, why? My understanding was that because these operations make a copy of the thing first, any existing iterators would be pointing at the unmodified original and would thus not care. Do I have a hole in my knowledge?

Edit 2: The precise code that's causing the problem is still a bit murky, but I can at least post the exception I'm seeing:


java.util.ConcurrentModificationException
    at java.util.concurrent.CopyOnWriteArrayList$COWSubList.checkForComodification(Unknown Source)
    at java.util.concurrent.CopyOnWriteArrayList$COWSubList.iterator(Unknown Source)
    at....

... where it points to a for-each loop instantiation in my code.

That COWSubList does seem to imply that my call to subList is the root of my problem; I'd still like to understand why.

Edit 3: *facepalm*

CopyOnWriteArrayList.subList() returns a List, not a CopyOnWriteArrayList. The list it returns is under no implied obligation to provide any of COWAL's protections. Which makes using subList() like this to remove elements a Very Bad Idea.

Don't know for certain if this is my culprit, but it's damned suspicious and needs to be corrected regardless.

2
It’s an old question, but it still should noted that list = list.subList(0, MAX_LIST_SIZE); is a horrible way to remove elements, as the sub list still references the original list which contains all elements, hence, this creates a memory leak. The canonical way of removing elements at the end, is list.subList(MAX_LIST_SIZE, list.size()).clear(); Generally, sub lists are a tool for applying arbitrary list operations to a part of a list, so they should be held temporarily throughout the operation only. - Holger

2 Answers

23
votes

CopyOnWriteArrayList.subLists throw ConcurrentModificationExceptions if the containing list changes out from underneath it:

public class ListTest {

  private static List<int[]> intList;

  public static void main (String[] args) {
    CopyOnWriteArrayList<Integer> cowal = new CopyOnWriteArrayList<Integer>();
    cowal.add(1);
    cowal.add(2);
    cowal.add(3);

    List<Integer> sub = cowal.subList(1, 2);
    cowal.add(4);
    sub.get(0); //throws ConcurrentModificationException
  }
}
-1
votes

Sbodd has the correct answer, but it sounds like using CopyOnWriteArrayList instead of ArrayList is just an attempt to mask the error. The true problem is an attempt to modify the underlying list while iterating over it. You need to find where in your code you are accessing it as such and remove that usage or work around it.