java.lang.IndexOutOfBoundsException:索引:0,大小:1
我有一个我能想象到的最奇怪的索引问题。我有以下看似无辜的代码:java.lang.IndexOutOfBoundsException:索引:0,大小:1,java,Java,我有一个我能想象到的最奇怪的索引问题。我有以下看似无辜的代码: int lastIndex = givenOrder.size() - 1; if (lastIndex >= 0 && givenOrder.get(lastIndex).equals(otherOrder)) { givenOrder.remove(lastIndex); } 在我看来,这是一次适当的预检。(此处的列表声明为list,因此无法直接访问最后一个元素,但这对问题来说并不重要。)我得到以
int lastIndex = givenOrder.size() - 1;
if (lastIndex >= 0 && givenOrder.get(lastIndex).equals(otherOrder)) {
givenOrder.remove(lastIndex);
}
在我看来,这是一次适当的预检。(此处的列表声明为list
,因此无法直接访问最后一个元素,但这对问题来说并不重要。)我得到以下堆栈跟踪:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 1
at java.util.ArrayList.rangeCheck(ArrayList.java:604) ~[na:1.7.0_17]
at java.util.ArrayList.remove(ArrayList.java:445) ~[na:1.7.0_17]
at my.code.Here:48) ~[Here.class:na]
在运行时,它是一个简单的ArrayList
。现在,索引0应该在界限之内
编辑 许多人认为引入同步可以解决这个问题。我毫不怀疑。但我的核心问题(诚然未被表达)是不同的:这种行为怎么可能呢 让我详细说明一下。我们有5个步骤:
lastIndex
(此处大小为1)ArrayList
检查边界,发现它们不合适ArrayList
构造异常消息并抛出IndexOutOfBoundsException:Index:0,Size:0
,这已经够糟糕了。但在过去的几个月里,我从未见过这样的例外
相反,我看到的是IndexOutOfBoundsException:Index:0,Size:1
,这意味着在步骤4之后但在步骤5之前,列表将获得一个元素。虽然这是可能的,但似乎不太可能出现上述现象。然而,每次错误发生时都会发生!作为一名数学家,我认为这是不可能的。但我的常识告诉我还有另一个问题
此外,查看ArrayList
中的代码,您可以看到那里运行了数百次的非常短的函数,并且没有任何可变变量。这意味着我非常希望hotspot编译器能够省略函数调用,使关键部分更小;而且,该方法省略了对大小
变量的双重访问,使得观察到的行为不可能。显然,这并没有发生
所以,我的问题是,为什么会发生这种情况,为什么会以这种奇怪的方式发生。建议同步并不是问题的答案(它可能是问题的解决方案,但这是另一回事)。这很可能是并发问题 在尝试访问索引之前/之后,大小会以某种方式被修改。请使用 在一个简单的
main
中测试,它可以工作:
List<String> givenOrder = new ArrayList<>();
String otherOrder = "null";
givenOrder.add(otherOrder);
int lastIndex = givenOrder.size() - 1;
if (lastIndex >= 0 && givenOrder.get(lastIndex).equals(otherOrder)) {
System.out.println("remove");
givenOrder.remove(lastIndex);
}
List givenOrder=new ArrayList();
字符串otherOrder=“null”;
添加(其他订单);
int lastIndex=givenOrder.size()-1;
if(lastIndex>=0&&givenOrder.get(lastIndex.equals)(其他顺序)){
系统输出打印项次(“删除”);
删除(lastIndex);
}
您是否处于线程安全进程中?您的
列表
被其他线程或进程修改 因此,我检查了引发异常的rangeCheck
方法的ArrayList
实现的源代码,我发现:
private void rangeCheck(int paramInt) //given index
{
if (paramInt < this.size) // compare param with list size
return;
throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt)); // here we have exception
}
正如您可能看到的,列表的大小(this.size
)被访问了2次。第一次读取它是为了检查条件,而条件没有完全填充,因此消息是为异常生成的。在为异常创建消息时,调用之间只有paramInt
是持久的,但列表的大小是第二次读取的。这就是我们的罪魁祸首
实际上,您应该得到消息:索引:0大小:0
,但用于检查的大小值不是本地存储的(微优化)。因此,在这两次读取之间,此.size
列表已更改
这就是信息误导的原因
结论:
这种情况在高度并发的环境中是可能的,并且很难重现。要解决此问题,请使用
synchronized
版本的ArrayList
(如@JordiCastillia)。此解决方案可能会影响性能,因为每个操作(添加/删除和可能获取)都将同步。另一种解决方案是将您的代码放入synchronized
块,但这只会同步这段代码中的调用,而且问题在将来仍然会发生,因为系统的不同部分仍然可以异步访问整个对象。共享列表
,请先添加一个元素,然后再检查ArrayList是否为空。另一个进程或线程正在修改ArrayList。我想你正在循环中使用它,而这个循环比你想象的要多。向我们显示您的循环。您必须引入此列表的已同步版本
或至少同步您发布的代码。这是一个并发问题,它揭示了ArrayList
internalst中的某种错误。如果@JordiCastilla回答有意义,则另一个线程可以在创建lastIndex后修改列表assigned@Antoniossss大小和错误不在同一行中,因此,如果另一个线程修改了列表
,则当列表中的对象正在运行时,此信息将不正确removed@GuillermoMerino我只是不明白,lastIndex
与我无关,只有那个例外。@GuillermoMerino在我看来,这是最可能的并发问题,但这里的例外不仅仅是误读,因为给定的索引是有界的。不幸的是,您的简单测试太简单了,因为它几乎总是有效的。很可能,而且不知何故,它没有回答这个问题。这很可能是一个并发问题
private String outOfBoundsMsg(int paramInt)
{
return "Index: " + paramInt + ", Size: " + this.size; /// OOOoo we are reading size again!
}