【发布时间】:2018-06-27 04:20:29
【问题描述】:
我最近遇到了一个意外错误(在不是我的代码中)导致ArrayList#contains 上的ArrayIndexOutOfBoundsException。这里的相关代码如下。
private static final List<String> list = new ArrayList<>();
static void register() {
update();
new Timer().schedule(new TimerTask() {
@Override
public void run() {
update();
}
}, 0, 21600000);
}
private static void update() {
list.clear();
new Thread(() -> {
List<String> other; //should always be the same length.
list.addAll(other);
}).start();
}
public static boolean contains(String string) { //called long after register
return list.contains(string); //throws ArrayIndexOutOfBounds
}
我很清楚ArrayList 不是线程安全的,它可以用Collections#synchronizedList 之类的东西来修复。我的问题是了解这个特定代码是如何抛出 ArrayIndexOutOfBoundsException 的。
异常的堆栈跟踪标识ArrayList#indexOf 中的以下代码。
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) //here
return i;
在我看来,这只有在size 大于elementData.length 时才会发生。据我了解,ArrayList#clear 实际上并没有减少后备数组的长度。对addAll 的调用应该只会增加其容量,并且size 总是在阵列扩展后更新。我不明白这怎么可能处于size 大于数组容量的状态。
我注意到一个特别的细节是Timer 的延迟为0,这意味着update() 被快速连续调用两次。我最好的猜测是 addAll 调用在某种程度上重叠,这使得列表中出现某种混合无效状态。
如果有人能解释这里发生了什么,那就太好了!
【问题讨论】:
-
请张贴围绕for循环的整个方法。我只能猜测
o和elementData代表什么值。 -
根据 Java 文档:如果多个线程同时访问 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则它必须在外部同步。 (结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)
-
@DiabolicWords 直接来自
ArrayList#indexOf,如上所述。我不明白这会如何真正影响这一点。 hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/… @smitty1 是的,我知道。我的问题主要与如何发生有关,尤其是了解多线程如何在基础层面上工作。
标签: java multithreading arraylist