定义
Iterable
表示一个对象是可以迭代的,也就是可以以某种顺序遍历集合中的所有元素。实现了Iterable
接口的类可以用于foreach
语法中
Iterable
接口只有一个iterator
方法需要实现,该方法返回一个Iterator
对象。Iterator
被称为迭代器,它是对集合进行迭代的主体。
所有的Collection都实现了Iterable接口。除了CopyOnWriteArrayList
以外,所有集合提供的迭代器都不是线程安全的,即使是Synchronized集合也不例外,也就是说如果一个线程中在迭代,另一个线程是可以对集合做出修改的,至于为什么Synchronized集合的迭代也没有实现线程安全的原因在于,迭代的过程一般耗时过长,如果在迭代时对集合上锁,就会极大影响程序的并发性,所以这一设计是为了性能的妥协。
为了检测迭代过程中是否存在另一个线程的并发修改,迭代器会记录通过当前迭代器对集合的修改次数,而集合本身包含了一个总的被修改的次数,如果迭代器发现这两个次数不一致,则说明在它迭代的同时可能有其他的线程进行并发的修改。为什么说是可能呢,因为如果迭代的过程中当前线程直接对集合进行add或者remove操作也会导致总的修改次数的增加。另外,这个机制并不一定能够检测出并发修改,我们可以从代码中看出原因:
// In the Iterator for ArrayList
public E next() {
checkForComodification(); // 检查修改次数是否一致
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
它首先检查修改次数是否一致,如果不一致就抛出异常,一致的话才返回下一个元素。但是有可能另一个线程在它检查完修改次数之后才对集合进行修改,这样的话在这一次的next方法的调用中就检查不出问题了。
不知道读者有没有这个疑问:为什么被设计用于单线程环境的ArrayList
等非线程安全的集合也需要在迭代的时候检查concurrent modification呢,按理说它们是应该不关心它们在多线程下的行为的,因为你根本就不应该在多线程环境中使用它们。我在Stackoverflow上提了这个问题:
![2c7e072d506f20a76d5d769b9a15a2c3.png](https://i-blog.csdnimg.cn/blog_migrate/98f5f573db87289be9a1fe2356f6848f.png)
不得不感叹在Stackoverflow上得到一个优质的回答是多么快... 我刚问了几分钟就得到了解答:
Don't mistake the "concurrent" in "concurrent modification" as referring only to multithreading.
You can get a ConcurrentModificationException in single-threaded code too:
List<String> list = new ArrayList<>();
list.add("");
Iterator<String> it = list.iterator();
list.add("");
it.next(); // ConcurrentModificationException
我的追问:
I wonder why adding elements to underlying collection is not allowed after creating the iterator, even when the iteration and the adding operations are in the same thread(So the underlying collection should not be corrupted)? Is this because the iteration should be considered as a "atomic" operation that when an iteration begins, the underlying collection should not be changed by outside actions, no matter which thread the change occurs in
对追问的解答:
it's a question of how the iterator should handle the change. If the insertion is after the current point in the iteration, is it obviously correct to include that in the elements returned by the iterator? If the insertion or deletion is at a point that the iterator has already passed, is it obviously correct to keep the iterator "where it was" (in terms of the index in the list; or in terms of between the same two elements)? Whichever way you silently ignore/handle it, it would have surprising behaviour
代码分析
Iterable
Iterable接口只有一个需要被实现的iterator
方法,除此之外,它还提供了两个default方法:
// 将action方法依次作用在集合中的所有元素上
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
// 这个好像用的不多,没有去深究它的功能了
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
Iterator
Iterator接口有两个方法必须要实现:
hasNext
: 返回这个集合中是否还有元素没有被遍历next
:返回下一个元素
有一个可选实现的方法:
remove
:移除上一个被遍历的元素
还有一个一般不需要被重新实现的实用方法:
// 类似于Iterable的forEach方法,只不过作用的对象是集合中还没有被遍历的元素
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}