concurrentarraylist_关于ArrayList的ConcurrentModificationException的一些思考

关于ArrayList的ConcurrentModificationException的一些思考

先来看一个来自于阿里java规范文档的例子:

List a = new ArrayList();

a.add("1");

a.add("2");

for (String temp : a) {

if("1".equals(temp)){

a.remove(temp);

}

}

这里的执行结果比较奇怪。(待会再分析)

分析ArrayList的源码,可以看到其(或者父类AbstractList)维护一个modCount的变量:

protected transient int modCount = 0;

根据文档,这个变量的作用是

The number of times this list has been structurally modified.

Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.

这个数字的描述的是list的结构性修改次数,结构性修改指的是,那些会改变list的长度或者破坏list的方式,使得正在进行的迭代产生错误结果。

我们来看看ArrayList中为什么会抛出ConcurrentModificationException:

I remove()方法(改变modCount值)

public E remove(int index) {

rangeCheck(index);

modCount++;//这里

E oldValue = elementData(index);

int numMoved = size - index - 1;

if (numMoved > 0)

System.arraycopy(elementData, index+1, elementData, index,

numMoved);

elementData[--size] = null; // clear to let GC do its work

return oldValue;

}

以及add()方法

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

private void ensureExplicitCapacity(int minCapacity) {

modCount++;//look这里

// overflow-conscious code

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

可以看出,remove()的时候是对modCount进行过修改,同样的在add()方法中也是,即添加删除多少次modCount就是多少。我们并没有看到ConcurrentModificationException的抛出,说明,无论我们怎么对ArrayList进行修改,只会改变modCount值而不会抛出异常。

那么是哪里抛出的呢,看下面:

II 迭代器Itr(抛出ConcurrentModificationException异常)

private class Itr implements Iterator {

int cursor; // index of next element to return

int lastRet = -1; // index of last element returned; -1 if no such

int expectedModCount = modCount;//获取迭代器的时候会初始化expectedModCount

public boolean hasNext() {

return cursor != size;

}

@SuppressWarnings("unchecked")

public E next() {

checkForComodification();//检查list是否被操作过,若没有继续

int i = cursor;

if (i >= size)

throw new NoSuchElementException();

Object[] elementData = ArrayList.this.elementData;

if (i >= elementData.length)

//数据没有被操作而合法的游标cursor又大于数组的长度,日了狗了,hasNext()为什么不用return cursor < size

throw new ConcurrentModificationException();

cursor = i + 1;

return (E) elementData[lastRet = i];

}

public void remove() {

if (lastRet < 0)

throw new IllegalStateException();

checkForComodification();

try {

ArrayList.this.remove(lastRet);

cursor = lastRet;

lastRet = -1;

expectedModCount = modCount;

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}

@Override

@SuppressWarnings("unchecked")

public void forEachRemaining(Consumer super E> consumer) {

Objects.requireNonNull(consumer);

final int size = ArrayList.this.size;

int i = cursor;

if (i >= size) {

return;

}

final Object[] elementData = ArrayList.this.elementData;

if (i >= elementData.length) {

throw new ConcurrentModificationException();

}

while (i != size && modCount == expectedModCount) {

consumer.accept((E) elementData[i++]);

}

// update once at end of iteration to reduce heap write traffic

cursor = i;

lastRet = i - 1;

checkForComodification();

}

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

从上面的代码可以看出在迭代器Itr的next(), remove()这两个方法在进行操作前都会去使用checkForComodification校验modCount是否一致,即保证当前操作前的list没有被改变,就像每次出门进门都会检查锁坏没有一样,若没有坏(modCount == expectedModCount)就继续开门锁门,若坏了(modCount != expectedModCount),就报警或者找修锁的(抛出ConcurrentModificationException)。

所以在多线程环境中,一个线程要添加删除,另一个线程迭代,很容易中招。

回到最开始的例子,这个例子可以正常的执行下去,最后a里只剩一个元素’2’。但是啊但是,若改成

...

if(“2”.equals(temp)) {

//......

}

...

此时运行会抛出ConcurrentModificationException,这跟我们刚刚分析的不一样啊,明明没有用迭代器,怎么会抛出ConcurrentModificationException。

没办法,看看异常信息吧,如下:

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)

at java.util.ArrayList$Itr.next(ArrayList.java:851)

at cc.rinoux.instantTest.main(instantTest.java:16)

......

果然还是迭代器产生的异常。什么时候调的迭代器?当然是foreach语句,foreach语句实际上是对iterator.hasNext()和next()的调用,先判断hasNext(),为true再next()获得元素(并非语法糖,涉及到Collection和Iterable,此处不赘述)。

但是为什么if(“1”.equals(temp))没有抛异常,而if(“2”.equals(temp))抛异常,这就涉及到上面注释里写的hasNext()为什么要这样写。

前者在完成第一次next操作后,cursor变为1,在remove,size变为1;继续遍历,先做hasNext判断,此时size为1 == cursor,直接返回false跳出循环遍历中止,因此没机会去抛出 ConcurrentModificationException。

后者则是,第一个遍历结果,不满足remove条件,因此modCount依然为0,hasNext()依然为true;第二个遍历结果满足,remove,modCount为1, size为1, cursor为2;在下一次foreach中,hasNext()通过,但是next()就不行了,因为modCount != expectedModCount,所以操作前检查抛出ConcurrentModificationException。

注意这里,cursor是大于size的,游标大于list长度的情况就发生了,所以hasNext()用的return cursor != size 而非 return cursor < size。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值