本文共 3002 字,大约阅读时间需要 10 分钟。
在实际开发过程中,我们经常会遇到遍历集合类的情况。但是其内部实现又是如何的呢?使用时需要注意的地方呢?本文一一道来。
Java集合类包含了List接口、Map接口和Set接口,分别看一下三者的便利方式。
for (int i = 0; i < list.size(); i++) { //doSomething } Iteratorit = list.iterator(); while (it.hasNext()) { //doSomething } for (String s2 : list) { //doSomething }
for (Integer in : map.keySet()) { //doSomething } Iterator> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); //doSomething } for (Map.Entry entry : map.entrySet()) { //doSomething }
Iteratorit = set.iterator(); while (it.hasNext()) { String value = it.next(); //doSomething } for(String s: set){ //doSomething }
总结一下,集合类的遍历方式包括迭代器和for循环。
在对集合迭代的过程中对集合进行一些修改的操作, 比如说add,remove之类的操作, 搞不好就会抛出ConcurrentModificationException, 这一点在API文档上也有说的。在迭代时只可以用迭代器进行删除。
我们以ArrayList为例,当我们使用迭代器遍历的时候,会频繁调用hasNext和next方法,其代码如下:
public boolean hasNext() { return cursor != size; } 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方法调用了checkForComodification方法,看一下内部实现:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
继续跟踪modCount和expectedModCount两个变量,在Itr类的成员变量里对expectedModCount初始化的赋值:
int expectedModCount = modCount;
而modCount是AbstractList中的一个protected的变量, 在对集合增删的操作中均对modCount做了修改, 记录的是操作次数。通过判断遍历操作前后modCount的值是否相等,继而判断是否抛出异常。
查看ArrayList源码可以看到add和remove方法都会修改modCount的值,HashMap的也含有一个名为modCount的内部变量,处理逻辑跟ArrayList类似,不再赘述。因此不合适的使用上述方法均可能产生ConcurrentModificationException。
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
在fail-safe机制下,任何对集合结构的修改都会在一个复制的集合上进行修改,因此不会抛出ConcurrentModificationException。但是fail-safe机制也有如下两个问题:1、需要复制集合,产生大量的无效对象,开销大;2、无法保证读取的数据是目前原始数据结构中的数据。
方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
方案二:使用CopyOnWriteArrayList来替换ArrayList。
CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。 该类产生的开销比较大,但是在两种情况下,它非常适合使用:
遇到这两种情况使用CopyOnWriteArrayList来替代ArrayList再适合不过了。
本文以常用集合类的遍历方式为起始,讲述了通过迭代器遍历可能引发的问题,并引出了内部的fail-fast机制,最后给出此机制的解决办法,希望能对读者有所启发。
转载地址:http://tsgmi.baihongyu.com/