1

While I was testing my own answer to get the output for this question, I got the following output for the given list content:

// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");

Output:

Item 1
Item 2
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at com.akefirad.tests.Main$1.run(Main.java:34)
    at java.lang.Thread.run(Thread.java:745)

But if one use the following list:

// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");

Output:

Item 1
Item 2

There is no exception in the output and only two first items will be printed. Can anyone explain why it behaves like this? Thanks

Note: the code is here.

EDITED: My question is why I don't have the third item printed (meaning the list is modified) and while there is no exception.

EDITED the code to produce the exception, please note the list content:

public class Main {

    public static void main(String[] args) throws InterruptedException
    {
        final ArrayList<String> list = new ArrayList<String>();
        list.add("Item 1");
        list.add("Item 2");
        list.add("Item 3");
        list.add("Item 4");

        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run ()
            {
                for (String s : list)
                {
                    System.out.println(s);
                    try
                    {
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

        Thread.sleep(2000);
        list.remove(0);
    }
}
Community
  • 1
  • 1
Rad
  • 4,292
  • 8
  • 33
  • 71
  • 1
    @TheNewIdiot: The code in the related question already does. – Jon Skeet Jul 25 '14 at 07:54
  • It would really help if you could make this question self-contained. I suspect the answer is simply that you've got a race condition between the different threads. It's completing the iteration between the second and third items being added. – Jon Skeet Jul 25 '14 at 07:55
  • @JonSkeet He is talking about the code which he posted as an answer I suppose. – AllTooSir Jul 25 '14 at 07:56
  • @TheNewIdiot: No, I've just spotted the last line of the post, which refers to code using an enhanced for loop. Using `iterator.remove()` won't help when the OP is trying to *add* items... and doing so in different threads. (It's *not* the normal "modifying in the loop".) – Jon Skeet Jul 25 '14 at 07:57
  • I didn't get what your conversation is about ;) Anyway the code is added. Only the one which throws the exception. If I add only three items to the list, it won't throw it anymore. – Rad Jul 25 '14 at 08:11
  • @Rad: I've edited my answer to give more details about why this is happening. – Jon Skeet Jul 25 '14 at 08:32

2 Answers2

4

The behavior you are trying to reproduce is highly timing-dependent.

You get the exception if and only if the two threads happen to overlap in time when modifying the list.

Otherwise, you do not get the exception.

Using Thread.sleep() cannot reliably force an overlap between two threads because the kernel can always decide to schedule threads arbitrarily after they awaken.


Update: The OP want to know whether exactly one of the following two cases must occur:

  • All three items are printed
  • Some of them printed and an exception is thrown

Jon Skeet's answer points out a case where less than three elements are printed, without an exception, which implies that the answer is no.


More generally, looking for a ConcurrentModificationException is not a reliable way of detecting multiple threads modifying and reading an object simultaneously. In fact, the Javadoc for the Exception addresses this point very specifically.

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • If I got it correctly, the output should be either three items printed or some of them printed and an exception, no matter what. I don't think data-race matters here, am I right? – Rad Jul 25 '14 at 08:13
  • @Rad, Not necessarily. You would have to consider every possible interleaving of the other thread code with the main thread's code to draw any conclusion about that. In order to do that, you would have to look at the implementation of the iterator for ArrayList. – merlin2011 Jul 25 '14 at 08:16
  • It might be the right question: Iterator implementation. Nevertheless, I expect that when the next() is called there are two possibilities: either the list is not modified or it is. If it is, then the exception is expected. If it is not modified the third item should be printed. – Rad Jul 25 '14 at 08:21
  • @Rad, Your conclusion is logically sound given the assumption that the iterator's `next()` method is atomic with respect to the three operations of reading a value, checking for concurrent modification, and throwing an exception. – merlin2011 Jul 25 '14 at 08:24
  • I do assume (not exactly what you said, anyway). Otherwise I cannot count the exception in such situation. Maybe I'm too greedy ;) Could you please update the answer so I can accept it. – Rad Jul 25 '14 at 08:30
  • Thanks for your updates! It's more clear than I asked :) – Rad Jul 25 '14 at 10:20
2

Fundamentally, you're modifying a list in one thread while iterating over it in another, and the list implementation you're using does not support that.

The ArrayList iterator implementation appears to only detect invalid modicifications on the call to next(), not on the call to hasNext(). So if you get into the last iteration of the loop before the remove() call, then you won't get an exception - hasNext() will just return false. On the other hand, if the remove() happens before the last call to next() (and if this is noticed on the other thread - the memory model comes into play here) then you'll get the exception. So for example, if you change your in-loop sleep to Thread.sleep(2500) then you'll get the exception at the start of the second iteration, because the remove() call will occur before it.

If you want to use a list in multiple threads and at least one of them is modifying it, you should use an implementation which supports that, such as CopyOnWriteArrayList.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    I think his question is more of "why do I sometimes get lucky" rather than "why did the Exception occur at all?" – merlin2011 Jul 25 '14 at 08:02
  • @merlin2011: and that's why I've got the paragraph about "if you're very lucky" – Jon Skeet Jul 25 '14 at 08:10
  • Fair enough. I made the comment only because you opened by trying to explain why the exception occurs at all. :) – merlin2011 Jul 25 '14 at 08:13
  • @merlin2011: Yes - it's not clear that the OP fully understood that part either... – Jon Skeet Jul 25 '14 at 08:27
  • @merlin2011: I've added more details now that I've looked more closely at the code. – Jon Skeet Jul 25 '14 at 08:31
  • +1, I like the detailed reasoning in the updated answer. – merlin2011 Jul 25 '14 at 08:47
  • Or simply the thread is not alive for third iteration. The application exists right before the third iteration. To check I did (not the same as your suggestion, the main thread) Thread.sleep(4000) which caused three items printed and the forth one left unprinted without any exception. – Rad Jul 25 '14 at 10:23
  • 1
    @Rad: By the time it's trying to print the 4th item, there *are* only 3 items. It's not a daemon thread as far as I can tell, so it *should* get to the end. Try adding `System.out.println("Finished");` after the loop. – Jon Skeet Jul 25 '14 at 10:25
  • You are right. It reaches the end. I was wrong. Thanks – Rad Jul 25 '14 at 10:33