2

Can someone please explain to me why do I get a ConcurrentModificationException if I just want to access an element of the list?

I was under impression that ConcurrentModificationException is thrown only when one thread iterates over a collection and another one tries to modify it. This is not my case though. I just want to get an element of the list.

Here is a code snippet which reproduces my problem (please don't judge the logic, I just wanted to reproduce the problem):

private static void checkConcurrentModificationException(){

    List<String> stringSpan = new ArrayList<String>();
    List<List<String>> clausesStrings = new ArrayList<List<String>>();
    List<String> testStringList = new ArrayList<String>();

    for (int i = 0; i < 121; i++) {
        testStringList.add("abc");
    }

    for (int i = 0; i < testStringList.size(); i++) {
        stringSpan.clear();
        if (i%2==0) {
            if (i>3) {
                for (int j = i+1; j < testStringList.size(); j++) {
                    if (j>i+4 || j>=testStringList.size()-1) {
                        stringSpan = testStringList.subList(i, j); //might be the problem
                        clausesStrings.add(stringSpan);
                        break;
                    }
                }
            }
        } 
    }
    System.out.println("Clause list size: "+clausesStrings.size());
    System.out.println("First clause: "+clausesStrings.get(0));
}

I analyzed the instructions and came to a conclusion that the problem lies in the stringSpan = testStringList.subList(i, j); but I'm not quite sure what happens behind and why it fails. Also, I tried to copy(without reference) the resulting list of lists clausesStrings to a new one but no luck so far.

Viorel Morari
  • 537
  • 3
  • 10
  • 1
    You can likely resolve the issue by copying the sublist rather than just keeping a reference to it: `clausesStrings.add(new ArrayList<>(stringSpan));` – sprinter Mar 27 '17 at 23:39
  • 2
    "I was under impression that ConcurrentModificationException is thrown only when one thread iterates over a collection and another one tries to modify it." - no, it has nothing to do with threads. It's thrown if an iterator detects that the collection has been modified in a way that doesn't go through the iterator itself. It can be thrown if the modification happened in the same thread, and it won't be thrown if a different thread modifies the collection through the iterator itself (with proper synchronization). – user2357112 Mar 27 '17 at 23:40
  • @user2357112 I don't see an iterator. – shmosel Mar 27 '17 at 23:40
  • @shmosel: True! Apparently some sublist implementations also throw ConcurrentModificationException, under similar conditions: detected modification to the backing list that wasn't performed through the sublist, regardless of what thread performed the modification. This doesn't seem to be documented. – user2357112 Mar 27 '17 at 23:44
  • 3
    @user2357112 are you aware that calling `clear` on your sublist will remove the items from the original list? Is that what you are intending to happen? As it stands your list will get shorter on each iteration. – sprinter Mar 27 '17 at 23:46
  • @sprinter: Wrong number there; I'm just a commenter. The OP's number starts with 37. – user2357112 Mar 27 '17 at 23:48
  • @user2357112 oops sorry. – sprinter Mar 27 '17 at 23:53
  • The sublist javadoc has a hint "The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list.". This is the case calling `clear` on another sublist. The `iterator` is being used in the `toString` method implicit called in the last System.out.println – user85421 Mar 31 '17 at 23:22

1 Answers1

-3

"I was under [the] impression that ConcurrentModificationException is thrown only when one thread iterates over a collection and another one tries to modify it." Nothing in the documentation says anything about CME happening only in a multithreaded scenario. How did you get that impression?

Give us the exact text, copy-pasted, of the exception message.

I suspect stringSpan.clear(); is your culprit. clear "Removes all of the elements from this list", and since stringSpan is a sublist, it's backed by the list over which you're iterating. So you also remove all those elements from testStringList, while you're iterating over it! Bam! ConcurrentModificationException.

EDIT:

For those who so eagerly downvoted my answer: TRY IT!

It works. When I ran the OP's code, CME! When I made the suggested changes,

run:
Clause list size: 58
First clause: [abc, abc, abc, abc, abc]
BUILD SUCCESSFUL (total time: 0 seconds)

NO CME! Take that, haters!

Here's the code:

public class SubModifier {
  static void checkConcurrentModificationException(){

    List<List<String>> clauses = new ArrayList<>();
    List<String> testItems = new ArrayList<>();

    for (int i = 0; i < 121; i++) {
      testItems.add("abc");
    }

    for (int i = 0; i < testItems.size(); i++) {
      if (i%2==0) {
        if (i>3) {
          for (int j = i+1; j < testItems.size(); j++) {
            if (j>i+4 || j>=testItems.size()-1) {
              List<String> span = testItems.subList(i, j);
              clauses.add(span);
              break;
            }
          }
        }
      } 
    }
    System.out.println("Clause list size: "+clauses.size());
    System.out.println("First clause: "+clauses.get(0));
  }

  public static void main(String[] args) {
    checkConcurrentModificationException();
  }
}
Lew Bloch
  • 3,364
  • 1
  • 16
  • 10
  • The way to figure that out is by going over the Javadocs for each operation you performed. – Lew Bloch Mar 27 '17 at 23:48
  • He's not iterating over anything. – shmosel Mar 28 '17 at 00:14
  • You mean other than in the loops? – Lew Bloch Mar 28 '17 at 00:28
  • The loops aren't iterating. Not in the sense that would throw a `ConcurrentModificationException`. – shmosel Mar 28 '17 at 00:29
  • One way to test my hypothesis is to get rid of the reuse of `stringSpan`, delete the lines `stringSpan.clear();` and the current declaration of `stringSpan1`, and change `stringSpan = testStringList.subList(i, j);` to `List stringSpan = testStringList.subList(i, j);`. – Lew Bloch Mar 28 '17 at 00:30
  • The loops are iterating, by definition, and `subList` is simply filled with warnings about not structurally modifying the list. Try it; if I'm right the experiment will show it. – Lew Bloch Mar 28 '17 at 00:32
  • Iterating by whose definition? Definitely not `ArrayList`'s. – shmosel Mar 28 '17 at 00:35
  • Your change works because `testStringList` is no longer modified. Nothing to do with concurrent iteration. – shmosel Mar 28 '17 at 00:40
  • It isn't "concurrent iteration", it's "concurrent modification". They didn't get a `ConcurrentIterationException`, they got a `ConcurrentModificationException`. While iterating. I guess code that works and fixes the problem in exactly the way I suggested isn't enough evidence for some people. Being fact-resistant is not an asset for computer programmers. – Lew Bloch Mar 28 '17 at 00:45
  • @shmosel, here's the definition of "iteration" in computer science: https://en.wikipedia.org/wiki/Iteration – Lew Bloch Mar 28 '17 at 00:46
  • You're completely missing the point. Happy to continue discussing when you're off your high horse. – shmosel Mar 28 '17 at 00:47
  • One more pointer: see which line is throwing the exception in the original example. – shmosel Mar 28 '17 at 00:51
  • I might be on a high horse but that doesn't change the facts. The fact is that the suggestion worked and the CME went away. The fact is that the definition of "iteration" is not tied to iterators. Feel free to use whatever terms you like, but being fact-resistant is not a useful trait for computer programmers. – Lew Bloch Mar 28 '17 at 01:18
  • The line that throws the CME is the line that got fubared by the change, not the line that caused the fubar. – Lew Bloch Mar 28 '17 at 01:21
  • The fact that the CME went away doesn't prove your diagnosis. I can make it go away by deleting the entire method, but it proves nothing. And I explained exactly why your change "fixes" the bug. The ArrayList documentation that discusses CME is clearly referring to the use of `iterator()` or `listIterator()`, not iteration via index, hence the irrelevance of the wikipedia definition. – shmosel Mar 28 '17 at 01:22
  • If you read said documentation, or do any amount of testing, you'll see that CME can be thrown by iterator methods, not by calling `list.get()`. – shmosel Mar 28 '17 at 01:23
  • Here's a snippet I hope will demonstrate that a) modification while iterating with an index does not throw CME, and b) modifying the backing list of a `subList` will throw a CME without iteration: http://ideone.com/0icN0l – shmosel Mar 28 '17 at 02:03