为什么要用modCount
在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中我们总能看到modCount的身影,modCount字面意思就是修改次数,所有使用modCount属性的全是线程不安全的,而且只有在本数据结构对应迭代器中才使用。
在源码中:
private class Itr implements Iterator<E> {
int cursor;//表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
int lastRet = -1; //表示上一个访问的元素的索引
int expectedModCount = modCount;//表示对ArrayList修改次数的期望值,它的初始值为modCount。
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)//如果modcount和expectedModCount不相等说明进行了结构性修改,抛出ConcurrentModificationException。
throw new ConcurrentModificationException();
}
}
我们以简单的Arraylist检测一下
public class TestArraylistmodCount {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("zhao");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String integer = iterator.next();
if (integer.equals("zhao"))
list.remove(integer); // 注意这个地方
}
}
}
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 FX_ArrayList.TestArraylistmodCount.main(TestArraylistmodCount.java:12)
在使用迭代器遍历的时候,如果使用ArrayList中的remove(int index) remove(Object o) remove(int fromIndex ,int toIndex) add等方法的时候都会修改modCount,在迭代的时候需要保持单线程的唯一操作,如果期间进行了插入或者删除,就会被迭代器检查获知,从而出现运行时异常。
事实上在我们remove掉这个元素之后 ,该ArrayList的size()的值就变为了0,而此时Iterator的游标cursor是 1 ,在ArrayList迭代器的hasNext()方法中
public boolean hasNext() {
return cursor != size;
}
//如果下一个访问的元素下标不等于ArrayList的大小,就表示有元素需要访问,这个很容易理解,如果下一个访问元素的下标等于ArrayList的大小,则肯定到达末尾了
当中判断出cursor 确实不等于 size,然后循环又继续跑了!如果我们不进行modCount和expectedModCount(创建迭代器的时候将当时的modCount赋值给expectedModCount),这个程序肯定会报ArrayIndexOutOfBoundsException,这样的异常显然不是应该出现的(这些运行时错误都是使用者的逻辑错误导致的,我们的JDK那么高端,不会出现使用错误,我们只抛出使用者造成的错误,而这个错误是设计者应该考虑的),为了避免出现这样的异常,定义了检查。又想,为什么不把hasNext()的判断改为cursor <=size呢?但是我们还有可能 add()这样的话就会导致数据混乱,事实上线程安全本身就不允许读的时候被修改。
所以在这里和大家建议,当大家遍历那些非线程安全的数据结构时,尽量使用迭代器