正如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尽管不涉及线程,但这将抛出一个错误。
误导性的异常名称使我认为我的问题是我处理多线程的结果。我已用实际问题更新了帖子标题。
我的代码大致如下所示:
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数组
有没有办法说“嘿,我已经完成了对该数组的修改,人们现在可以安全地读取它了”?还是组织我的代码的更好方法?
您遇到的最明显的问题是,您在使用ArrayList.subList()时似乎并不了解它的真正作用:
返回此列表部分的视图。返回的列表由该列表支持。
您要存储的messagesInFlight是视图,而不是副本。当您从中删除第一条消息时messages,实际上是在删除通话messagesInFlight后您拥有的所有消息subList()。因此,在for循环之后,将出现完全不同的消息,并且前n条消息将完全丢失。
至于为什么看到错误,您会看到- subList()特别允许对子列表和原始列表进行非结构化更改(非结构化意味着替换元素,而不添加或删除元素),并且文档中的示例也展示了如何通过修改子列表来修改原始列表。但是,不允许修改原始列表,然后再通过子列表访问它,这可能会导致ConcurrentModifcationException,类似于更改通过迭代器迭代的列表时发生的情况。
| 归档时间: |
|
| 查看次数: |
83 次 |
| 最近记录: |