3

I have the following code

public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        Arrays.stream("hello how are you".split(" ")).forEach(s -> list.add(s));

        Iterator<String> it = list.iterator();
        ListIterator<String> lit = list.listIterator();

        while (it.hasNext()) {
            String s = it.next();
            if (s.startsWith("a")) {
                it.remove();
            } else {
                System.out.println(s);
            }
        }

        System.out.println(list);

        // {here}

        while (lit.hasNext()) {
            String s = lit.next();
            if (s.startsWith("a")) {
                lit.set("1111" + s);
            } else {
                System.out.println(s);
            }
        }

        System.out.println(list);
}

Here, after iterating through the Iterator, I try to iterate through the ListIterator. But the code throws a ConcurrentModificationException. I do the modification using the ListIterator only after the Iterator is done, but why do I get this exception.

When I initalize the ListIterator at {here} instead at the top, the code runs perfectly.

  1. Isn't ConcurrentModificationException thrown when the list is being modified by two threads simultaneously?
  2. Does initializing the iterator, create a lock on the list ? If yes, then why does Java let us to initialize an Iterator after it has already been initialized by another Iterator?
Mureinik
  • 297,002
  • 52
  • 306
  • 350
v1shnu
  • 2,211
  • 8
  • 39
  • 68

4 Answers4

2

Isn't ConcurrentModificationException thrown when the list is being modified by two threads simultaneously ?

Not necessarily. The ConcurrentModificationException indicates that the list has been structurally changed (except by the Iterator's own remove method) after the Iterator was created. This could be due to multiple threads using the same list, or it could be due to an attempt to for example remove items from an ArrayList inside a for each loop without using an Iterator.

Does initializing the iterator, create a lock on the list ?

No, there are no locks. When the Iterator is created it records the modCount of the ArrayList (a crude representation of the list's state that is incremented on every structural change). If an iterator detects a change to the List's modcount that wasn't caused by its own methods the exception is thrown.

You are getting the exception from the second iterator because of the structural changes made to the list between the second iterator being instantiated and used.

why does Java let us to initialize an Iterator after it has already been initialized by another Iterator?

The ArrayList does not keep track of all the iterators that it has created, or their state. To do so would massively complicate the implementation. The modCount approach is not perfect and is a bit crude, but it is simple and identifies many real bugs.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • So does that mean that no two iterators can act on the same list ? – v1shnu Oct 08 '17 at 16:46
  • You can have two iterators iterating over the same list simultaneously. If they do not add or remove elements, no exception will be thrown. – Paul Boddington Oct 08 '17 at 16:48
  • @PaulBoddington why can't we perform update operations when iterating the same list simultaneously. Does Iterators keep their own copy while iterating and update the final list when all the iteration is over or it updates the original List on each pass? – Shubhendu Pramanik Oct 08 '17 at 17:03
  • @ShubhenduPramanik No they don't keep a copy, they just keep track of a single `int` value that changes when elements are added or removed. – Paul Boddington Oct 08 '17 at 17:32
1

You have to load the second iterator after you have used the first iterator. Otherwise the second iterator "thinks" the list hasn't been changed but in reality it did. Because the list got changed the second iterator react like "Wait a minute, that shouldn't be here/gone" and throws a ConcurrentModificationException.

It let you initialize the iterator at any time. When you don't change the content you might be even fine with it and you don't get a ConcurrentModificationException because nothing has been changed.

Progman
  • 16,827
  • 6
  • 33
  • 48
1

A ConcurrentModificationException may be thrown whenever you try to use an invalid iterator - which can happen whenever you create an iterator and then modify the underlying collection from a different access point. Here, lit is initialized and then the list is modified through it, so its invalidated, which explains the exception.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
0

ListIterator throws ConcurrentModificationException it there is a modification in list after its creation. In your code you have created Iterator and ListIterator at the same time an later you are deleting something from the list which causes ConcurrentModificationException.

To avoid this changes your code to below one. You just need to move the ListIterator initialization after the operations of iterator.

public static void main(String[] args) {

    List<String> list = new ArrayList<>();
    Arrays.stream("hello how are you".split(" ")).forEach(s -> list.add(s));

    Iterator<String> it = list.iterator();

    while (it.hasNext()) {
        String s = it.next();
        if (s.startsWith("a")) {
            it.remove();
        } else {
            System.out.println(s);
        }
    }

    System.out.println(list);

    ListIterator<String> lit = list.listIterator();

    while (lit.hasNext()) {
        String s = lit.next();
        if (s.startsWith("a")) {
            lit.set("1111" + s);
        } else {
            System.out.println(s);
        }
    }

    System.out.println(list);
}
Rahul
  • 73
  • 1
  • 2
  • 12