Java 为什么 iterator.remove 不抛出 ConcurrentModificationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24856811/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why iterator.remove does not throw ConcurrentModificationException
提问by javafan
What does iterator.remove()
do differently from list.remove()
, so that iterator does not throw an exception and list.remove()
does throw one? In the end, both are modifying the collection size.
与 , 有iterator.remove()
什么不同list.remove()
,以便迭代器不会抛出异常并且list.remove()
会抛出异常?最后,两者都在修改集合大小。
Please ignore multi-threading here. I am just talking about a for-each loop and an iterator loop. As far as I know, a for-each loop creates an iterator only internally.
请忽略这里的多线程。我只是在谈论 for-each 循环和迭代器循环。据我所知,for-each 循环仅在内部创建迭代器。
I am confused.
我很迷惑。
采纳答案by Stephen C
ConcurrentModificationException
is not thrown by Iterator.remove()
because that is the permittedway to modify an collection while iterating. This is what the javadocfor Iterator
says:
ConcurrentModificationException
不会被抛出,Iterator.remove()
因为这是在迭代时修改集合的允许方式。这是javadocforIterator
所说的:
Removes from the underlying collection the last element returned by this iterator (optional operation). This method can be called only once per call to next(). The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
从底层集合中移除此迭代器返回的最后一个元素(可选操作)。每次调用 next() 只能调用一次此方法。如果在迭代过程中以除调用此方法以外的任何方式修改了底层集合,则迭代器的行为是未指定的。
If you change the collection being iterated any other way, then you are liable to get an exception, depending on the implementation of iterator, and the collection (or whatever) that you are iterating. (Some collection classes won't give you a ConcurrentModificationException
: check the respective javadocs to see how they specify the behavior of theiriterators)
如果您以任何其他方式更改正在迭代的集合,那么您很可能会遇到异常,具体取决于迭代器的实现以及您正在迭代的集合(或其他任何东西)。(一些集合类不会给你一个ConcurrentModificationException
:检查各自的 javadoc 以了解它们如何指定其迭代器的行为)
You are also liable to get an exception if you have two iterators on the same collection, and you remove via one of them.
如果您在同一个集合上有两个迭代器,并且您通过其中一个进行删除,您也可能会遇到异常。
What iterator.remove does different from list.remove that iterator does not throw exception while list.remove does throw?
iterator.remove 与 list.remove 有何不同,即迭代器不会抛出异常而 list.remove 会抛出异常?
Reason #1. If you had a non-concurrent collection being updated simultaneously from two places on the same call stack, the behavior would break the design invariant for the iteration1. An iteration of a non-concurrent collection is guaranteed to see all of the elements in the collection exactly once. (By contrast, with concurrent collections these guarantees are relaxed.)
原因#1。如果您有一个非并发集合从同一调用堆栈上的两个位置同时更新,则该行为将破坏迭代1的设计不变量。保证非并发集合的迭代只看到集合中的所有元素一次。(相比之下,对于并发集合,这些保证是放松的。)
Reason #2. Non-concurrent collection types are not implemented to be thread-safe. Therefore, you could have race conditions and memory anomalies if the collection and iterator are used to update the collection by different threads. This is not strongreason because you will have these problems anyway. However, having the updates happening in two different ways makes the problem worse.
原因#2。非并发集合类型未实现为线程安全的。因此,如果不同线程使用集合和迭代器更新集合,则可能会出现竞争条件和内存异常。这不是强有力的理由,因为无论如何您都会遇到这些问题。但是,以两种不同的方式进行更新会使问题变得更糟。
I am just talking about for-each loop and iterator loop. As far as I know for-each loop internally create iterator only.
我只是在谈论 for-each 循环和迭代器循环。据我所知,for-each 循环仅在内部创建迭代器。
That is correct. A for-each loop is really just syntactic sugar for a while
loop using an iterator.
那是正确的。for-each 循环实际上只是while
使用迭代器的循环的语法糖。
On the other hand, if you use a loop like this:
另一方面,如果您使用这样的循环:
for (int i = 0; i < list.size(); i++) {
if (...) {
list.remove(i);
}
}
you won't get ConcurrentModificationException
, but you will need to adjust the index variable for the elements that you delete, and updates by another thread are liable to cause you to skip elements or visit them more than once2.
你不会得到ConcurrentModificationException
,但你需要为你删除的元素调整索引变量,另一个线程的更新很可能导致你跳过元素或多次访问它们2。
1 - To achieve "exactly once" iteration behavior, when you remove an element via the collection object, the iterator data structure would need to be updated to keep it in step with what has happened to the collection. This is not possible in the current implementations because they don't keep links to the outstanding iterators. And if they did, they would need to use Reference
objects or risk memory leaks.
1 - 要实现“恰好一次”迭代行为,当您通过集合对象删除元素时,需要更新迭代器数据结构以使其与集合中发生的情况保持一致。这在当前的实现中是不可能的,因为它们不保留指向未完成迭代器的链接。如果他们这样做了,他们将需要使用Reference
对象或冒内存泄漏的风险。
2 - Or even get an IndexOutOfBoundsException
. And if the collection is not concurrent / properly synchronized, you can get worse problems.
2 - 甚至得到一个IndexOutOfBoundsException
. 如果集合不是并发/正确同步,你可能会遇到更糟糕的问题。
回答by Stuart Marks
I think you mean, if you're iterating a list, why does list.remove()
cause a ConcurrentModificationException
to be thrown whereas iterator.remove()
does not?
我想你的意思是,如果你正在迭代一个列表,为什么会list.remove()
导致 aConcurrentModificationException
被抛出而iterator.remove()
不会?
Consider this example:
考虑这个例子:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) {
if (iter.next().equals("b")) {
// iter.remove(); // #1
// list.remove("b"); // #2
}
}
If you uncomment line #1, it will work fine. If you uncomment line #2 (but leave #1 commented) then it will cause the subsequent call to iter.next()
to throw ConcurrentModificationException
.
如果您取消注释第 1 行,它将正常工作。如果您取消注释第 2 行(但保留第 1 行注释),则会导致后续调用iter.next()
throw ConcurrentModificationException
。
The reason is that the iterator is a separate object that has some references to the internal state of the underlying list. If you modify the list while the iterator is in operation, it could cause the iterator to behave badly, e.g. by skipping elements, repeating elements, indexing off the end of the array, etc. It attempts to detect such modifications and so it throws ConcurrentModificationException
if it does.
原因是迭代器是一个单独的对象,它有一些对底层列表内部状态的引用。如果在迭代器运行时修改列表,它可能会导致迭代器表现不佳,例如跳过元素、重复元素、索引数组末尾等。它尝试检测此类修改,因此ConcurrentModificationException
如果确实如此。
Removing elements through the iterator works and does not cause exceptions, because this updates the underlying list andthe iterator's state that refers to the internals of the list, so everything can stay consistent.
通过迭代器移除元素有效并且不会导致异常,因为这会更新底层列表和引用列表内部的迭代器状态,因此一切都可以保持一致。
However, there is nothing special about iterator.remove()
that makes it work in all cases. If there are multipleiterators iterating over the same list, modifications made by one will cause problems for the others. Consider:
然而,没有什么特别之处iterator.remove()
使它在所有情况下都能工作。如果有多个迭代器在同一个列表上迭代,其中一个的修改会给其他的造成问题。考虑:
Iterator<String> i1 = list.iterator();
Iterator<String> i2 = list.iterator();
i1.remove();
i2.remove();
We now have two iterators pointing into the same list. If we modify the list using one of them, it disrupts the operation of the second, so the call to i2.remove()
will result in ConcurrentModificationException
.
我们现在有两个迭代器指向同一个列表。如果我们使用其中之一修改列表,则会中断第二个的操作,因此调用i2.remove()
将导致ConcurrentModificationException
.
回答by user207421
Because it is the iterator that throws the exception. If you call List.remove()
it doesn't know about the removal, only that something has changed under its feet. If you call Iterator.remove()
it knows the current element was removed and what to do about it.
因为是迭代器抛出异常。如果你打电话给List.remove()
它,它不知道移除,只知道它脚下的东西发生了变化。如果你调用Iterator.remove()
它就知道当前元素已被删除以及如何处理它。
回答by Mifeet
Here is an example how things could go wrong if collection iterators didn't check for modifications of the underlying collection. This is how ArrayLists
's iterator is implemented:
下面是一个示例,如果集合迭代器不检查底层集合的修改,事情会如何出错。这是ArrayLists
迭代器的实现方式:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) throw new NoSuchElementException();
// ...
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
// ...
ArrayList.this.remove(lastRet);
// ...
cursor = lastRet;
lastRet = -1;
}
Let's look at an example:
让我们看一个例子:
List list = new ArrayList(Arrays.asList(1, 2, 3, 4));
Iterator it = list.iterator();
Integer item = it.next();
We remove the first element
我们删除第一个元素
list.remove(0);
If we want to call it.remove()
now, the iterator would remove number 2because that's what field lastRet
points to now.
如果我们想it.remove()
现在调用,迭代器将删除数字 2,因为这就是lastRet
现在指向的字段。
if (item == 1) {
it.remove(); // list contains 3, 4
}
This would be incorrect behavior! The contract of the iterator states that remove()
deletes the last element returned by next()
but it couldn't hold it's contract in the presence of concurrent modifications. Therefore it chooses to be on the safe side and throw an exception.
这将是不正确的行为!迭代器的契约声明remove()
删除最后一个返回的元素,next()
但它不能在并发修改的情况下保持它的契约。因此它选择安全起见并抛出异常。
The situation may be even more complex for other collections. If you modify a HashMap
, it may grow or shrink as needed. At that time, elements would fall to different buckets and an iterator keeping pointer to a bucket before rehashing would be completely lost.
对于其他集合,情况可能更加复杂。如果修改 a HashMap
,它可能会根据需要增长或缩小。那时,元素将落入不同的桶,并且在重新散列之前保持指向桶的指针的迭代器将完全丢失。
Notice that iterator.remove()
doesn't throw an exception by itself because it is able to update boththe internal state of itself and the collection. Calling remove()
on two iterators of the same instance collection would throw, however, because it would leave one of the iterators in an inconsistent state.
请注意,iterator.remove()
是因为它能够更新本身并不抛出异常双方自身的内部状态和收集。remove()
然而,调用同一实例集合的两个迭代器会抛出异常,因为它会使其中一个迭代器处于不一致的状态。
回答by Mat8
public class ArrayListExceptionTest {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
Iterator<String> it1 = list1.iterator();
ArrayList<String> list2 = new ArrayList<String>();
list2.add("a");
try {
while (it1.hasNext()) {
list1.add(it1.next());
}
} catch (ConcurrentModificationException e) {
e.printStackTrace();
}
it1 = list1.iterator();
while (it1.hasNext()) {
System.out.println(it1.next());
}
it1 = list1.iterator();
try {
while (it1.hasNext()) {
if (it1.next().equals("a"))
list1.retainAll(list2);
}
} catch (ConcurrentModificationException e) {
e.printStackTrace();
}
it1 = list1.iterator();
while (it1.hasNext()) {
System.out.println(it1.next());
}
it1 = list1.iterator();
Iterator<String> it2 = list1.iterator();
it1.remove();
it2.remove();
}
}
You can see the above 3 cases
可以看到以上3个案例
case 1: Modification made by adding the element, hence when next() function is used it resulted in ConcurrentModificationException.
情况 1:通过添加元素进行修改,因此在使用 next() 函数时会导致 ConcurrentModificationException。
case 2: Modification made by using the retain(), hence when next() function is used it resulted in ConcurrentModificationException.
情况 2:通过使用 retain() 进行修改,因此当使用 next() 函数时,会导致 ConcurrentModificationException。
case 3: Will throw java.lang.IllegalStateException not ConcurrentModificationException.
情况 3:会抛出 java.lang.IllegalStateException 而不是 ConcurrentModificationException。
Output:
输出:
a
b
c
a
a
a
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.rms.iteratortest.ArrayListExceptionTest.main(ArrayListExceptionTest.java:21)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.rms.iteratortest.ArrayListExceptionTest.main(ArrayListExceptionTest.java:37)
Exception in thread "main" java.lang.IllegalStateException
at java.util.ArrayList$Itr.remove(ArrayList.java:872)
at com.rms.iteratortest.ArrayListExceptionTest.main(ArrayListExceptionTest.java:55)