天天看点

ArrayList中modCount的作用

在ArrayList中有个成员变量modCount,继承于AbstractList。

这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。这到底有什么用呢?

先看下面一段测试代码:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                list.remove(str);
            }
    }
}
           

在使用迭代器遍历集合的时候同时修改集合元素。因为ArrayList被设计成非同步的,所以理所当然会抛异常。但是该抛什么异常才能说明该问题呢?

首先得了解ArrayList的迭代器

public Iterator<E> iterator() {
        return new Itr();
    }
           

在调用list.iterator()的时候返回的是一个Itr对象,它是ArrayList中的一个内部类。

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = 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();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
           

主要关注3个点:

1、expectedModCount的初值为modCount

2、hasNext的判断条件为cursor!=size,就是当前迭代的位置不是数组的最大容量值就返回true

3、next和remove操作之前都会先调用checkForComodification来检查expectedModCount和modCount是否相等

  如果没checkForComodification去检查expectedModCount与modCount相等,这个程序肯定会报ArrayIndexOutOfBoundsException

  这样的异常显然不是应该出现的(这些运行时错误都是使用者的逻辑错误导致的,我们的JDK那么高端,不会出现使用错误,我们只抛出使用者造成的错误,而这个错误是设计者应该考虑的),为了避免出现这样的异常,定义了检查。所以抛出ConcurrentModificationException异常更能说明问题。

将测试代码改成如下:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                if(str.equals("d")){
                    list.remove(str);
                }else{
                    System.out.println(str);
                }
            }
    }
}
           

输出却是 a b c。

  因为在删除 d 的时候cursor为4,size也变成了4。所以hasNext就返回为true了,循环结束,从而后面的元素也不会输出了。

又想,为什么不把hasNext()的判断改为cursor <=size()呢?但是我们还有可能 add()这样的话就会导致数据混乱,事实上线程安全本身就不允许读的时候被修改。

这种问题在多线程情况下,操作同一集合很容易暴露,就算改成同步的Vector问题还是会存在,需要使CopyOnWriteArrayList。

继续阅读