尝试运行`.size()`方法时ArrayList抛出`ConcurrentModificationException`

ste*_*esu 2 java

更新资料

正如Jiri Tousek所指出的那样,我的代码中抛出的错误误导了许多业余(和经验丰富)的Java开发人员。相反的是名字似乎暗示,ConcurrentModificationException不能有任何与多线程。考虑以下代码:

import java.util.List;
import java.util.ArrayList;

class Main {
  public static void main(String[] args) {
    List<String> originalArray = new ArrayList<>();
    originalArray.add("foo");
    originalArray.add("bar");
    List<String> arraySlice = originalArray.subList(0, 1);
    originalArray.remove(0);
    System.out.println(Integer.toString(arraySlice.size()));
  }
}
Run Code Online (Sandbox Code Playgroud)

ConcurrentModificationException尽管不涉及线程,但这将抛出一个错误。

误导性的异常名称使我认为我的问题是我处理多线程的结果。我已用实际问题更新了帖子标题。

原文(标题:如何通知Java您已完成对线程中ArrayList的修改?)

我的代码大致如下所示:

class MessageQueue {
    private List<String> messages = new ArrayList<>();
    private List<String> messagesInFlight = new ArrayList<>();

    public void add(String message) {
        messages.add(message);
    }

    public void send() {
        if (messagesInFlight.size() > 0) {
            // Wait for previous request to finish
            return;
        }

        messagesInFlight = messages.subList(0, Math.min(messages.size, 10));
        for( int i = 0; i < messagesInFlight.size(); i++ )
        {
            messages.remove(0);
        }

        sendViaHTTP(messagesInFlight, new Callback() {
            @Override
            public void run() {
                messagesInFlight.clear();
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这在我的代码中用于分析目的。我每10秒钟messageQueue.send()从计时器呼叫一次,每当发生感兴趣的事件时,我都会呼叫messageQueue.add()。此类在大多数情况下都有效 –我可以添加消息,并通过HTTP发送消息,并且当HTTP请求完成时,将运行回调

问题出在计时器的第二个滴答声上。当我上线时if (messagesInFlight.size() > 0) {,出现以下错误:

java.util.ConcurrentModificationException
        at java.util.ArrayList$SubList.size(ArrayList.java:1057)
Run Code Online (Sandbox Code Playgroud)

似乎我无法.size()在一个线程(第二个计时器的回调)中读取数组的,因为它认为该数组仍在被另一个线程(第一个计时器的回调)修改。但是,我希望在调用之后,第一个计时器的线程被破坏并清理sendViaHTTP,因为没有其他代码可以执行。此外,HTTP请求在500毫秒内完成,因此整整9.5秒过去了,没有任何东西触及空messagesInFlight数组

有没有办法说“嘿,我已经完成了对该数组的修改,人们现在可以安全地读取它了”?还是组织我的代码的更好方法?

Jir*_*sek 5

您遇到的最明显的问题是,您在使用ArrayList.subList()时似乎并不了解它的真正作用:

返回此列表部分的视图。返回的列表由该列表支持。

您要存储的messagesInFlight视图,而不是副本。当您从中删除第一条消息时messages,实际上是在删除通话messagesInFlight后您拥有的所有消息subList()。因此,在for循环之后,将出现完全不同的消息,并且前n条消息将完全丢失。


至于为什么看到错误,您会看到- subList()特别允许对子列表和原始列表进行非结构化更改(非结构化意味着替换元素,而不添加或删除元素),并且文档中的示例也展示了如何通过修改子列表来修改原始列表。但是,不允许修改原始列表,然后再通过子列表访问它,这可能会导致ConcurrentModifcationException,类似于更改通过迭代器迭代的列表时发生的情况。