谜团:我们都知道集合的迭代器可以遍历集合,但是在遍历集合的时候不能对集合进行修改,否则则会报错,这是为啥呢**
迭代器源码分析:(这是ArrayList的内部类Itr)
private class Itr implements Iterator<E> {
int cursor; // 游标指针的初始位置,默认为0
int lastRet = -1;
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
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();
}
}
我们先关注两点:
1.迭代的属性expectedModCount默认赋值为modCount
2.我们看看next()方法里第一行代码"checkForComodification()"方法:
它会去判断modCount和expectedModCount是否相等,如果不相等的话则会抛异常。
换句话来说,我们在遍历集合的时候每遍历一个元素都会去这么判断
那么问题来了,什么时候modCount和expectedModCount不会相等呢??
我们可以去看看ArrayList的源码,会发现当进行add()和remove()方法的时候,都会进行一个“modCount++”的操作。
那么我们如果我们在遍历集合的时候,进行增或者删了,那么modCount会进行自增,在遍历的时候modCount和expectedModCount就不会相等了
这就是为什么迭代器遍历集合的时候不能进行增删的原因了,怎么样,是不是so easy!!
----------------------------------------------------分割线----------------------------------------------------
既然谜团解开了,那么又有一个问题来了,我就是犯贱,我就是想在遍历集合的时候进行增删操作,咋办??
我们看看上面那块代码,就是ArrayList的内部类Itr的remove()方法,如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet); //它本质上还是调用了ArrayList的remove方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; //注意这里,它又让expectedModCount和modCount相等了
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
我们刚才已经知道,集合之所以不能遍历的时候进行增删,本质就是因为expectedModCount和modCount不相等。但是如果你调用的是迭代器Itr的remove()方法的话,它又会让expectedModCount和modCount变得相等。这样子在调用next()遍历的时候就不会报错了
ArrayList就是用的这种方式巧妙了的化解了多线程下的不安全操作。modCount其实就相当于一个版本号,每次集合进行增删的时候都会进行自增(其实HashMap也有modCount),然后每次操作的时候都会去判断这个版号号是否和期望的一样。
这种思路其实也类似于CAS和我们的数据库的乐观锁,想了解的小伙伴们可以自己去拓展哦,这里就不详细说明了
so?我们的问题是不是就这么解决了呢