为什么当多个线程使用迭代器同时处理同一个数组列表时,这段代码不会抛出 ConcurrentModificationException

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

此处有 2 个线程处理同一个数组列表,一个线程读取元素​​,另一个线程删除特定元素。我希望这会抛出 ConcurrentModificationException 。但是它不抛出为什么?

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();
    }


}

您已经为两个线程实例覆盖了 start 方法,而不是 run,并且这些方法在主执行线程中完成,因此,不会同时执行线程,也不会 ConcurrentModificationThreadException 可以在这里发生。

你犯了 2 个比较常见的错误。

ConcurrentModificationException 不是关于并发

你会认为,鉴于这个名字,CoModEx 是关于并发的。不是。就像,你不需要线程来获取它。在这里,这个简单的代码将抛出它:

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

那是因为 CoModEx 是由迭代器抛出的,只是意味着这件事发生了:

  1. 有人做了一个迭代器。
  2. 有人以某种方式更改了列表(而不是通过迭代器的 .remove() 方法)
  3. 某人运行在#1
  4. 中制作的迭代器上的任何相关方法

所以,在上面,foreach 循环隐式地创建了一个迭代器(#1),然后 list.remove 方法被调用(#2),然后通过再次点击 foreach 循环,我们调用了一个相关的该迭代器 (.hasNext()) 上的方法,瞧,CoModEx 发生了。

事实上,多线程 的可能性较小:毕竟,您应该假设如果您从多个线程与某个对象进行交互,那么 它已损坏,因为这种行为是未指定的,因此,你有一个错误,更糟糕的是,很难测试一个错误。如果你在迭代它的同时从另一个线程修改一个普通的简数组列表,你不能保证一个CoModEx。你可能会明白。你不可以。电脑可能会离开办公桌,在百老汇碰碰运气。 “未指定的行为”是一种很好的说法:“不要,说真的。它会一直伤害你,因为你无法测试它;这将在你开发它的整个过程中正常工作,并且只要你付出对大假发客户的重要演示,它会以令人尴尬的方式在你身上失败。

从多个线程与一个对象交互的方式非常小心:检查特定对象的文档明确说明发生了什么(即使用 java.util.concurrent 包中的东西,它是专门用 'interact with it from more than one thread' 牢记用例),否则,请使用锁定。这些都是棘手的事情,所以在 java 中执行多线程的通常方法是首先不要共享状态。尽可能多地隔离,反转控制,并使用具有内置事务内在特性的消息传递策略,例如消息队列(rabbitmq 和朋友)和数据库(具有事务)。

如何使用线程

您重写 run() 方法,然后通过调用 start 方法启动线程。或者更好的是,不要覆盖 运行,在创建线程实例时传递一个 Runnable 实例。

这就是你使用线程的方式。你没有 - 你覆盖了开始,这意味着启动这些线程根本不会创建一个新线程,它只是 运行 是你线程中的有效负载。这解释了您的具体情况,但是您尝试做的事情(通过弄乱另一个线程的列表来证明 CoModEx)也不会让您获得 CoModEx - 它会让您出现未指定的行为,这意味着一切都会发生。