Why does ArrayList not throw ConcurrentModificationException when modified from multiple threads?

后端 未结 5 1460
被撕碎了的回忆
被撕碎了的回忆 2021-01-14 03:38

ConcurrentModificationException : This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.<

相关标签:
5条回答
  • 2021-01-14 04:14

    I'm quoting a large section of the ArrayList Javadoc for your benefit. Relevant portions that explain the behavior you are seeing are highlighted.

    Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:

    List list = Collections.synchronizedList(new ArrayList(...));

    The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

    Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. 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.

    ArrayLists will generally throw concurrent modification exceptions if you modify the list structurally while accessing it through its iterator (but even this is not an absolute guarantee). Note that in your example you are removing elements from the list directly, and you are not using an iterator.

    If it tickles your fancy, you can also browse the implementation of ArrayList.remove, to get a better understanding of how it works.

    0 讨论(0)
  • 2021-01-14 04:16

    But it runs OK. No exception is thrown. Why?

    Simply because that concurrent modification is permissible.

    The description of the exception says this:

    "This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible."

    The clear implication is that are (or may be) permissible concurrent modifications. And in fact for the standard Java non-concurrent collection classes, concurrent modifications are permitted ... provided that they don't happen during an iteration.


    The reasoning behind this is that for the non-concurrent collections, modification while iterating is fundamentally unsafe and unpredictable. Even if you were to synchronize correctly (and that isn't easy1), the result would still be unpredictable. The "fail-fast" checks for concurrent modifications were included in the regular collection classes because this was a common source of Heisenbugs in multi-threaded applications that used the Java 1.1 collection classes.

    1- For instance, the "synchronizedXxx" wrapper classes don't, and can't synchronize with iterators. The problem is that iteration involves alternating calls to next() and hasNext(), and the only way to do a pair of method calls while excluding other threads is to use external synchronization. The wrapper approach isn't practical in Java.

    0 讨论(0)
  • 2021-01-14 04:23

    The reason you are not receiving a ConcurrentModificationException is that ArrayList.remove does not throw one. You can probably get one by starting an additional thread that iterates through the array:

    final List<String> tickets = new ArrayList<String>(100000);
    for (int i = 0; i < 100000; i++) {
        tickets.add("ticket NO," + i);
    }
    for (int i = 0; i < 10; i++) {
        Thread salethread = new Thread() {
            public void run() {
                while (tickets.size() > 0) {
                    tickets.remove(0);
                    System.out.println(Thread.currentThread().getId()+"Remove 0");
                }
            }
        };
        salethread.start();
    }
    new Thread() {
        public void run() {
            int totalLength = 0;
            for (String s : tickets) {
                totalLength += s.length();
            }
        }
    }.start();
    
    0 讨论(0)
  • 2021-01-14 04:37

    I don't think 'concurrent' means thread-related in this case, or at least it doesn't necessarily mean that. ConcurrentModificationExceptions usually arise from modifying a collection while in the process of iterating over it.

    List<String> list = new ArrayList<String>();
    for(String s : list)
    {
         //modifying list results in ConcurrentModificationException
         list.add("don't do this");     
    
    }
    

    Note that the Iterator<> class has a few methods that can circumvent this:

    for(Iterator it = list.iterator(); it.hasNext())
    {
         //no ConcurrentModificationException
         it.remove(); 
    }
    
    0 讨论(0)
  • 2021-01-14 04:38

    Because you are not using an iterator, there is no chance of a ConcurrentModificationException being thrown.

    Calling remove(0) will simply remove the first element. It might not be the same element intended by the caller if another thread removes 0 before execution completes.

    0 讨论(0)
提交回复
热议问题