【问题标题】:Why does ArrayList not throw ConcurrentModificationException when modified from multiple threads?为什么 ArrayList 从多个线程修改时不会抛出 ConcurrentModificationException?
【发布时间】:2016-06-20 06:47:01
【问题描述】:

ConcurrentModificationException : 当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常。

以上是来自 javadoc 的 ConcurrentModificationException 定义。

所以我尝试测试以下代码:

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

代码很简单。 10 个线程从 arraylist 对象中删除元素。 可以确定多个线程访问一个对象。但它运行正常。不会抛出异常。 为什么?

【问题讨论】:

    标签: java multithreading arraylist thread-safety


    【解决方案1】:

    为了您的利益,我引用了ArrayList Javadoc 的大部分内容。突出显示解释您所看到的行为的相关部分。

    请注意,此实现不同步。如果多个线程 同时访问一个 ArrayList 实例,并且至少其中一个 线程在结构上修改列表,它必须同步 外部。 (结构修改是添加或 删除一个或多个元素,或显式调整后备数组的大小; 仅仅设置元素的值不是结构性的 修改。)这通常是通过同步一些 自然封装列表的对象。如果不存在这样的对象, 该列表应使用 Collections.synchronizedList “包装” 方法。这最好在创建时完成,以防止意外 对列表的非同步访问:

    列表列表 = Collections.synchronizedList(new ArrayList(...));

    这个类的 iterator 和 listIterator 方法返回的迭代器 快速失败:如果列表在之后的任何时间进行了结构修改 迭代器以任何方式创建,除了通过迭代器自己的 删除或添加方法,迭代器会抛出一个 并发修改异常。因此,面对并发 修改,迭代器快速而干净地失败,而不是 在不确定的时间冒着任意的、不确定的行为的风险 未来。

    请注意,不能保证迭代器的快速失败行为 因为一般来说,不可能做出任何硬性保证 在存在不同步的并发修改时。快速失败 迭代器尽最大努力抛出 ConcurrentModificationException 基础。因此,编写一个依赖的程序是错误的 关于这个例外的正确性:快速失败的行为 迭代器应该只用于检测错误。

    如果您在结构上修改列表同时通过它的迭代器访问它

    ArrayLists 通常会抛出并发修改异常(但这也不是绝对的保证)。请注意,在您的示例中,您直接从列表中删除元素,并且没有使用迭代器。

    如果您喜欢它,您还可以浏览ArrayList.remove 的实现,以更好地了解它的工作原理。

    【讨论】:

      【解决方案2】:

      我不认为'并发'在这种情况下意味着与线程相关,或者至少它不一定意味着。 ConcurrentModificationExceptions 通常源于在迭代过程中修改集合。

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

      请注意,Iterator&lt;&gt; 类有一些方法可以规避这一点:

      for(Iterator it = list.iterator(); it.hasNext())
      {
           //no ConcurrentModificationException
           it.remove(); 
      }
      

      【讨论】:

      • +1 简短而简单。并发并不一定意味着并行(如您的回答所示)。
      【解决方案3】:

      您没有收到ConcurrentModificationException 的原因是ArrayList.remove 没有抛出一个。您可能可以通过启动一个遍历数组的附加线程来获得一个:

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

      【讨论】:

        【解决方案4】:

        因为您没有使用迭代器,所以不可能抛出 ConcurrentModificationException

        调用remove(0) 只会删除第一个元素。如果另一个线程在执行完成之前删除 0,它可能不是调用者想要的 same 元素。

        【讨论】:

          【解决方案5】:

          但它运行正常。不会抛出异常。为什么?

          仅仅是因为允许同时进行修改。

          异常的描述是这样的:

          “当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常。”

          明确的暗示是(或可能)允许并发修改。事实上,对于标准的 Java 非并发集合类,并发修改是允许的……前提是它们不会在迭代期间发生。


          这背后的原因是,对于非并发集合,在迭代时修改从根本上是不安全和不可预测的。即使您要正确同步(这并不容易1),结果仍然是不可预测的。并发修改的“快速失败”检查包含在常规集合类中,因为这是使用 Java 1.1 集合类的多线程应用程序中 Heisenbugs 的常见来源。

          1- 例如,“synchronizedXxx”包装类不会,也不能与迭代器同步。问题是迭代涉及对next()hasNext() 的交替调用,并且在排除其他线程的同时进行一对方法调用的唯一方法是使用外部同步。包装器方法在 Java 中不实用。

          【讨论】:

            猜你喜欢
            • 2012-03-17
            • 2014-09-11
            • 1970-01-01
            • 1970-01-01
            • 2017-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-18
            相关资源
            最近更新 更多