美文网首页
fail-tail 机制:ConcurrentModificat

fail-tail 机制:ConcurrentModificat

作者: 博弈史密斯 | 来源:发表于2018-07-03 21:09 被阅读0次

    对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。下面我们就来讨论以下这个异常出现的原因以及解决办法。

    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        list.add(i);
    }
    
    for (int i = 0; i < list.size(); i++) {
        if (i == 2) {
            list.remove(i);
        }
    }
    

    上面的代码不会有问题,看下面的代码:

    //foreach,不需要下标。内部由iterator实现的
    for (Integer i : list) {
        if (i == 2) {.
            list.remove(i);
        }
    }
    

    上面的代码会报错:ConcurrentModificationException

    我们看一下 ArrayList 的源码,首先看下 remove:

    public E remove(int index) {
        ...
        modCount++;
        ...
    }
    

    我们省略了一些代码,在 remove 中维护了 modCount值,每次调用 modCount 都会自增1.

    上面我们说到,foreach会转为 iterator 实现,我们看下 iterator 的代码:

    public E next() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    
        ...
    
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
    
        ...
    }
    

    我们看到,在 iterator 的 next 方法中,有两处抛出 ConcurrentModificationException 的地方。
    第二处是数组越界,我们不关注。

    我们看下第一处:modCount != expectedModCount。

    • modCount:
      是指 list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会自增;
    • expectedModCount:
      期望的 ModCount,是指Iterator现在期望这个list被修改的次数是多少次。

    expectedModCount 值是在 iterator 的add 和 remove 方法中维护:

    //iterator 中的 add 方法
    public void add(E e) {
        ...
        expectedModCount = modCount;
    }
    
    //iterator 中的 remove 方法
    public void remove() {
        ...
        expectedModCount = modCount;
    }
    
    

    从上面看到,在 iterator的 add 和 remove 中 同步 expectedModCount 和 modCount。
    所以,正常情况下 expectedModCount 和 modCount 都是同步的。

    而在 foreach 中,调用 ArrayList 的 remove 方法,导致 modCount 加1,
    而 没有调用 iterator 的 remove 方法,导致 modCount 没有同步给 expectedModCount,
    在 iterator 的 next 方法中抛出异常。

    所以在单线程中,有两种方式避免 ConcurrentModificationException:

    1. 在 foreach 或对iterator 做循环遍历中,通过调用 iterator 的 add 或 remove,
      让 expectedModCount 和 modCount 保持同步。
      Iterator<Integer> iterator = list.iterator();
      while (iterator.hasNext()) {
          Integer integer = iterator.next();
          if (integer == 2)
              iterator.remove(); 
      }
      
    2. 不使用 iterator,通过 list 的下标进行遍历,调用 list 的 add 或 remove 方法:
      for (int i = 0; i < list.size(); i++) {
          if (i == 2) {
              list.remove(i);
          }
      }
      

    多线程下,如果有一个线程通过 iterator 遍历,
    另一个线程通过 List 的 remove 等进行修改,则会抛出 ConcurrentModificationException 异常:

    ArrayList<Integer> list = new ArrayList<>();
    
    private void printAll() {
        for (Integer i : list) {
            Log.i("zyb", String.valueOf(list.get(i)));
        }
    }
    
    private void removeByIndex(int index) {
        for (int i = 0; i < list.size(); i++) {
            if (i == index) {
                list.remove(i);
            }
        }
    }
    

    在 Main 方法下:

    for (int i = 0; i < 100; i++) {
        list.add(i);
    }
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            printAll();
        }
    }).start();
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            removeByIndex(50);
        }
    }).start();
    

    解决方式 是 在 printAll、removeByIndex 两个方法前 添加 synchronized 修饰。
    或者 ArrayList 改为 CopyOnWriteArrayList。

    Vector 不能解决上面的问题,虽然 Vector 中 每个方法都添加了 synchronized 关键字,iterator 中的方法也添加了 synchronized 关键字,譬如 当调用 iterator 的 next 方法,next执行完,释放锁,另一个线程获得锁,调用 List 的 remove 方法,释放锁,iterator next 方法获得锁,依然会出现 modCount != expectedModCount 的问题。

    使用 Vector 的时候线程并不是总是安全的,这是为什么呢?

    从上面的例子 我们可以联想,就算一个类中,对每个方法 添加 synchronized ,也不能保证就是线程安全的。

    相关文章

      网友评论

          本文标题:fail-tail 机制:ConcurrentModificat

          本文链接:https://www.haomeiwen.com/subject/szjruftx.html