Search code examples
javaiteratorconcurrentmodification

Why this code doesn't throw ConcurrentModificationException when multiple threads work on same arraylist at the same time using iterator


Here 2 threads work on same arraylist and one thread read the elements and another thread remove a specific element . I expect this to throw ConcurrentModificationException . But it is not throwing why?

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;


public class IteratorStudies {

    public static final ArrayList<String> arr ;

    static{

        arr = new ArrayList<>();

        for(int i=0;i<100;i++) {
            arr.add("someCommonValue");
        }
        arr.add("someSpecialValue");
    }

    private static Integer initialValue = 4;


    public static void main(String x[]) {


     Thread t1 = new Thread(){
          @Override
          public void start(){
              Iterator<String> arrIter = arr.iterator();
              while(arrIter.hasNext()){
                  try {
                      String str = arrIter.next();
                      System.out.println("value :" + str);
                  }catch(ConcurrentModificationException e){
                      e.printStackTrace();
                  }
              }
                System.out.println("t1 complete:"+arr);

          }
      };


        Thread t2 = new Thread(){
            @Override
            public void start(){
                Iterator<String> arrIter = arr.iterator();
                while(arrIter.hasNext()){
                    String str = arrIter.next();
                    if(str.equals("someSpecialValue")){
                        arrIter.remove();
                    }
                }

                System.out.println("t2 complete:"+arr);

            }
        };
        
        
        t2.start();
        t1.start();
    }


}

Solution

  • You've made 2 somewhat common mistakes.

    ConcurrentModificationException is not about concurrency

    You'd think, given the name, that CoModEx is about concurrency. It's not. As in, you don't need threads to get it. Here, this trivial code will throw it:

    void example() {
        var list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        for (String elem : list) {
            if (elem.equals("a")) list.remove(elem);
        }
    }
    

    That's becauseCoModEx is thrown by iterators and simply means this happened:

    1. Somebody made an iterator.
    2. Somebody changed the list somehow (and not via the iterator's .remove() method)
    3. Somebody runs any relevant method on the iterator made in #1

    So, in the above, the foreach loop implicitly makes an iterator (#1), then the list.remove method is invoked (#2), then by hitting the foreach loop again, we call a relevant method on that iterator (.hasNext()), and, voila, CoModEx occurs.

    In fact, multithreaded is less likely: After all, you should assume that if you interact with some object from multiple threads, that it is broken, in that behaviour is unspecified, thus, you have a bug, and worse, a hard to test for one. If you modify a plain jane arraylist from another thread whilst iterating over it, you are not guaranteed a CoModEx. You may get it. You may not. The computer may walk off the desk and try its luck on broadway. "Unspecified behaviour" is a nice way of saying: "Don't, seriously. It'll hurt the whole time because you cannot test it; this will work fine the entire time you are developing it, and juuust as you're giving that important demo to big wig client, it'll fail on you, in embarassing ways".

    The way to interact with one object from multiple threads is very carefully: Check the docs of the specific object explicitly states what happens (i.e. use stuff from the java.util.concurrent package which is specifically designed with 'interact with it from more than one thread' use cases in mind), and failing that, use locking. These are tricky things, so the usual way to do multi-threading in java is to not have shared state in the first place. Isolate as much as you can, invert control, and use messaging strategies that have built-in transactional intrinsics, such as message queues (rabbitmq and friends) and databases (which have transactions).

    How to use Threads

    You override the run() method, and then start the thread by calling the start method. Or better yet, don't override run, pass a Runnable instance along as you create the thread instance.

    That's how you use thread. You didn't - you overrode start, which means starting these threads doesn't make a new thread at all, it just runs the payload in your thread. That explains your specific case, but what you're trying to do (witness CoModEx by messing with a list from another thread) doesn't get you a CoModEx either - it gets you unspecified behaviour, which means anything goes.