15

The following code throws a java.util.ConcurrentModificationException, as expected:

   public void test(){
      ArrayList<String> myList = new ArrayList<String>();

      myList.add("String 1");
      myList.add("String 2");
      myList.add("String 3");
      myList.add("String 4");
      myList.add("String 5");

      for(String s : myList){
         if (s.equals("String 2")){
            myList.remove(s);
         }
      }
   }

However, the following code does not throw the Exception, while I expect it to be thrown:

   public void test(){
      ArrayList<String> myList = new ArrayList<String>();

      myList.add("String 1");
      myList.add("String 2");
      myList.add("String 3");

      for(String s : myList){
         if (s.equals("String 2")){
            myList.remove(s);
         }
      }
   }

The difference is that the first list contains 5 items, while the second list contains 3. The JVM used is:

java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

The question: why does the second piece of code NOT throw the java.util.ConcurrentModificationException?

user207421
  • 305,947
  • 44
  • 307
  • 483
Gijs Overvliet
  • 2,643
  • 3
  • 28
  • 35
  • I have asked exactly same question. – Braj Jul 27 '14 at 12:02
  • 2
    Actually it is not the same question. You asked why `remove` doesn't fail. Different question, with a different answer. – Stephen C Jul 27 '14 at 12:11
  • (The reason that `remove` doesn't fail is because the test for concurrent modifications doesn't happen in that call!) – Stephen C Jul 27 '14 at 12:18
  • possible duplicate of [Why doesnt hasnext,hasprevious etc methods of Iterator or listIterator check for ConcurrentModificationException](http://stackoverflow.com/questions/21450191/why-doesnt-hasnext-hasprevious-etc-methods-of-iterator-or-listiterator-check-for) – Kumar Abhinav Jul 27 '14 at 13:23
  • 1
    possible duplicate of [It does not throw exception ConcurrentModificationException](http://stackoverflow.com/questions/24556487/it-does-not-throw-exception-concurrentmodificationexception) – Peter O. Jul 27 '14 at 15:54
  • This is explained here https://bugs.java.com/bugdatabase/view_bug?bug_id=4902078 – sankar Mar 30 '23 at 11:50

2 Answers2

20

The iterator returned from ArrayList.iterator() in the implementation we're apparently both using only checks for structural modification in calls to next(), not in calls to hasNext(). The latter just looks like this (under Java 8):

public boolean hasNext() {
    return cursor != size;
}

So in your second case, the iterator "knows" that it's returned two elements, and that the list only has two elements... so hasNext() just returns false, and we never end up calling next() the third time.

I would view this as an implementation detail - basically the checking not being as strict as it could be. It would be entirely reasonable for hasNext() to perform a check and throw an exception in this case too.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • In spite of the "disclaimer" in the javadoc for ArrayList, I'm not sure whether this isn't an oversight (bug), mainly because it isn't an unsynchronized structural change done from another thread. - The standard test checkForComodification() compares just two ints. – laune Jul 27 '14 at 12:11
  • 4
    @laune - Whether or not it is an oversight, it is not a "bug" because the implementation is conforming to the spec. Besides the code has been like this since (at least) Java 6. – Stephen C Jul 27 '14 at 12:13
  • @StephenC Does your "spec" have a link (other than ArrayList's javadoc)? (If a bug slips through more than one release then this doesn't make it sacrosanct.) – laune Jul 27 '14 at 16:28
  • 2
    @laune Why wouldn't the ArrayList's javadoc be the spec? It specifies the behavior of ArrayList, including the fact that ConcurrentModificationException isn't reliable. – Colonel Thirty Two Jul 27 '14 at 16:59
  • @ColonelThirtyTwo Why can't the confession be the commandment? I'm ready to concede that not all unsynchronized interactions can be detected anyway, but why on earth should the one and only List.remove() of element at index List.size()-2 go by undetected and result in skipping the last element whereas all other List.remove calls cause ConcModExc? – laune Jul 27 '14 at 17:12
  • 1
    @laune: because if hasNext returns true, it will call next, which will detect the error. – Jon Skeet Jul 27 '14 at 19:04
  • @JonSkeet Sorry, but you've left me. If hasNext() returns false, you don't call next(), and the CME may remain undetected. This is what happens for any List.remove( List.size() - 2 ) – laune Jul 27 '14 at 19:08
  • 4
    @laune Whether it would be *better* for `hasNext` to make an attempt to detect concurrent modification doesn't change the fact that, per the class's specification, this behavior is not a bug. Perhaps the implementer had some performance concerns or just plain felt that the rarity of this scenario justified leaving the check out. Maybe they didn't think of it at all. Who knows? Either way, it's not a bug. – Chris Hayes Jul 27 '14 at 19:29
  • 2
    @laune: Yes, that's exactly my point - that's why it goes undetected, but others don't. If you were asking the more philosophical question of why it's implemented that way, Chris's comment says all I would say. – Jon Skeet Jul 27 '14 at 19:48
9

Note also the last paragraph of the ArrayList documentation summary:

Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

If you're worrying about forcing lists to be read-only, use the Collections.unmodifiableList method, instead of checking for ConcurrentModificationException, which as mentioned above is not guaranteed to be thrown in all relevant cases.

Peter O.
  • 32,158
  • 14
  • 82
  • 96