ConcurrentModificationException产生和CopyOnWriteArrayList 迭代数据过期问题

Copy-On-Write简称COW,是一种用于程序设计的优化策略。JDK有两种Copy-On-Write容器,CopyOnWriteArrayList和CopyOnWriteArraySet。

除了加锁外,其实还有一种方式可以防止并发修改异常,这就是将读写分离技术(不是数据库上的)。

先回顾一下一个常识:

1、JAVA中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,几乎不需要CPU时间。

2、JAVA中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。

package com.coderman.collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * CopyOnWriteArrayList 数据过期的情况
 * 时候读多写少的情况.
 * @Author zhangyukang
 * @Date 2020/7/24 15:16
 * @Version 1.0
 **/
public class CopyOnWriteArrayListTest2 {
    public static void main(String[] args) {
        List<Integer> list= new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Iterator<Integer> iterator1 = list.iterator();
        list.add(6);
        ListIterator<Integer> iterator2 = list.listIterator();
        System.out.println(list);
        System.out.println("---------第一次遍历-----------");

        while (iterator1.hasNext()){
            Integer next = iterator1.next();
            System.out.print(next+"\t");
        }
        System.out.println();

        System.out.println("---------第二次遍历-----------");

        while (iterator2.hasNext()){
            Integer next = iterator2.next();
            System.out.print(next+"\t");
        }
    }
}

会发现程序抛出 ConcurrentModificationException异常

image.png

原因
迭代器的next()方法

        @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];
        }

如果modCount != expectedModCount 就会抛出这个异常

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

其中 modCount 是ArrayList中定义的一个变量初始值为0.代表的含义是list修改的次数,
它在调用add(),remove()....方法后值会加一

protected transient int modCount = 0;

add方法

   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

remove方法

    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;
    }

而 expectedModCount的值是代表期望修改的值,它在创建迭代器的时候就被固定下来了
int expectedModCount = modCount;

    /**
     * An optimized version of AbstractList.Itr
     */
    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;
        }

因此回到上面的示例代码,迭代器一创建时的时候,因为调用的add()方法5次,此时的 expectedModCount = modCount =5
但是后面又调用了add()方法一次,将6添加进去,此时 modCount= 6, 而 expectedModCount =5 不变,因此在迭代时候, 检查发现 expectedModCount ! = modCount 就抛出了并发修改异常.

可以打个断点看看(6!=5) 没毛病.

image.png

接下来将 ArrayList 改成 CopyOnWriteArrayList 看看,一下是运行的结果

我擦,竟然没毛病,这是为啥呢????

[1, 2, 3, 4, 5, 6]
---------第一次遍历-----------
1	2	3	4	5	
---------第二次遍历-----------
1	2	3	4	5	6	

CopyOnWriteArrayList 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,并不直接修改原有数组对象,而是对原有数据进行一次拷贝,将修改的内容写入副本中。写完之后,再将修改完的副本替换成原来的数据,这样就可以保证写操作不会影响读操作了。

但是会存在遍历的时候数据过期问题,会发现遍历的结果跟迭代器创建的时期有关系, 因为 当add()执行之后,新副本产生,此时的迭代器还没有反应过来,仍然是之前的集合 .

# 多线程   JUC  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×