9.2 ArrayList&CopyOnWriteArrayList详解

9.2 ArrayList&CopyOnWriteArrayList详解

1.0 ArrayList

ArrayList是Java集合框架中的一个重要的类,它继承于AbstractList,实现了List接口,是一个长度可变的集合,提供了增删改查的功能,允许null的存在。ArrayList类还是实现了RandomAccess接口,可以对元素进行快速访问。实现了Serializable接口,说明ArrayList可以被序列化 。

     ArrayList<String> list = new ArrayList();
        list.add("a");
		//线程向数组中添加数据
        new Thread(()->{
            for(int i=0;i<1000;i++){
                list .add("i"+i) ;
            }
        }).start();
		//线程读数据
        new Thread(()->{
            for ( String  s: list) {
               /* if("a".equals(s)){
                    list.remove(s) ;
                }*/
                System.out.println(s);
            }
        }).start();

上面的代码执行会报ConcurrentModificationException,为什么呢?因为Failfast机制。

1.1 Fail-fast 机制及ArrayList源码分析

​ 快速失败系统,通常设计用于停止有缺陷的过程,这是一种理念,在进行系统设计时优先考虑异常情况,一旦发生异常,直接停止运行并上报。

​ ArrayList的 foreach 循环实际就是调用 ArrayList的iterator()方法循环,部分代码如下

public Iterator<E> iterator() {
            return listIterator();
}
        public ListIterator<E> listIterator(final int index) {
            checkForComodification();
            rangeCheckForAdd(index);
            final int offset = this.offset;

            return new ListIterator<E>() {
                int cursor = index;
                int lastRet = -1;
                //expectedModCount的初始化值。
                int expectedModCount = ArrayList.this.modCount;

                public boolean hasNext() {
                    return cursor != SubList.this.size;
                }

                @SuppressWarnings("unchecked")
                public E next() {
                    //数组是否修改
                    checkForComodification();
                    int i = cursor;
                    if (i >= SubList.this.size)
                        throw new NoSuchElementException();
                    Object[] elementData = ArrayList.this.elementData;
                    if (offset + i >= elementData.length)
                        throw new ConcurrentModificationException();
                    cursor = i + 1;
                    return (E) elementData[offset + (lastRet = i)];
                }

从源码可知,ArrayList的循环中多个方法的开始就是检测是否有修改,checkForComodification源码如下

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

从源码中可以发现,这个异常时变量expectedModCount和modCount不相等造成的,从代码中可以看出modCount是ArrayList的成员变量,表示集合被修改的次数,当ArrayList创建时就存在,初始值为0;expectedModCount是 iterator()迭代器方法的一个局部变量初始值和modCount一致,只有迭代器修改了集合,expectedModCount才会修改。看下ArrayList的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;
    }

这个方法会修改modCount,所以会导致两个值不相等报出异常。add方法中modCount也会增加,源码的注释有,不相信自己可以去看源码。

从上面的代码逻辑推理如果想不报错,那么操作ArrayList尽量用迭代器来操作为什么呢?因为expectedModCount是迭代器中的局部变量,只有迭代器才能操作它。除了这种解决方法还有其他方法吗?有可以用copyOnWriteArrayList代替ArrayList,因为copyOnWriteArrayList是线程安全的。

2.0 CopyOnWriteArrayList

CopyOnWriteArrayList 是 ArrayList 的线程安全版本,读取无锁,写时有锁。适用于 写少读多的场景。

2.1 CopyOnWriteArrayList线程安全原理分析
 public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //数组复制
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //把新的数组赋值于真实的数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

从上面的代码可知,安全的方式就是:在写时先加锁,在把数组复制一份,在新的数组上操作 ,最后用新数组覆盖原数组。

2.2 存在的问题及解决方法

CopyOnWriteArrayList用空间换时间的思想提高性能,因为写的操作都会复制出一个副本,若数组比较大,就会导致堆空间的内存急剧增加,频繁引发full GC,从而影响系统性能。

解决方法:用ReentrantLock 自定义一个线程安全的 ArrayList,分别定义个读锁和写锁,读写和写写用互斥锁 ,读读不互斥,这样即线程安全又能避免数据拷贝。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苹水相峰

你的打赏是对我最大的肯定

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值