列表for循环之再思考-CopyOnWriteList

前景提要

前几天发了篇关于循环遍历列表的博文,下面继续补充为什么CopyOnWriteList可以进行遍历删除不报错,以及为什么不建议CopyOnWriteList进行频繁删除或新增。

源码分析

我们已经知道了list不能在循环中删除新增的本质原因是因为modCount和exceptModCount的值不一致导致的。

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
			list.add("1");
			list.add("2");
			list.add("3");
			list.add("4");
			Iterator<String> iterator = list.iterator();
			while (iterator.hasNext()){
				String next = iterator.next();
				if(next.contains("2")){
					list.remove(next);
				}
			}

下面分析CopyOnWriteList为什么可以,直接上核心代码remove()方法

public boolean remove(Object o) {
        //拿到现在的数组
        Object[] snapshot = getArray();
        //这个方法返回元素所在的下标,下面会简单介绍
        int index = indexOf(o, snapshot, 0, snapshot.length);
        //如果返回小于0,则返回false,否则执行remove方法
        return (index < 0) ? false : remove(o, snapshot, index);
    }
 private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }

indexOf():
接收一个要搜索的元素 o,当前的数组 elements,搜索的起始索引 index,以及搜索范围的结束索引 fence。它遍历从 index 到 fence(不包括 fence)的数组元素,如果找到与 e 相等的元素,则返回该元素的索引;如果遍历完整个范围都没有找到,则返回 -1 表示未找到

我们可以看到关键代码是remove方法:

 private boolean remove(Object o, Object[] snapshot, int index) {
        //通过 ReentrantLock 加锁,确保在修改数组的过程中不会被其他线程打断
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //getArray方法,拿到现在的数组
            Object[] current = getArray();
            int len = current.length;
            //如果传入的快照数组 snapshot 不等于当前的数组 current,说明在获取快照之后有其他线程修改了数组。因此,需要重新查找要删除元素的索引
            if (snapshot != current) findIndex: {
            //使用 findIndex 标签的循环来重新查找索引。这里只遍历到 index 和 len 中的较小值,因为我们已经知道在 snapshot 中索引 index 处的元素是需要删除的
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
             //如果在遍历过程中发现 current 和 snapshot 在某个位置上的元素不同,并且该位置的元素等于要删除的对象 o,则更新 index 并跳出循环。
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                //如果循环结束后 index 大于等于 len,说明 o 不在当前数组中,返回 false
                if (index >= len)
                    return false;
             //如果 current[index] 仍然等于 o,则不需要进一步查找,直接跳出循环
                if (current[index] == o)
                    break findIndex;
            //如果以上条件都不满足,调用 indexOf 方法重新在 current 数组中查找 o 的索引。
                index = indexOf(o, current, index, len);
                //如果 indexOf 返回的索引小于 0,说明 o 不在当前数组中,返回 false
                if (index < 0)
                    return false;
            }
            //如果找到了要删除的元素的索引,创建一个新的数组 newElements,其长度比当前数组少一个元素。然后使用 System.arraycopy 方法将当前数组中的元素复制到新数组中,同时跳过要删除的元素。
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
           //调用 setArray 方法将 newElements 设置为当前数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

我们能够发现,这块代码并没有对modCount的改动,核心就是通过复制了一份新的数组,再进行赋值操作(此处跳过需要删除的元素),因此迭代器部分初始exceptModCount也是被赋值为modCount的0,迭代器对复制出来的删除元素并不感知,因此不会报错。
我们其实分析add()方法也能发现,他底层进行了复制数组操作,再赋值

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

因此我们说并不建议在高写删的场景下进行使用copyOnWriteList,因为底层会进行大量的复制数组操作,这对于内存和性能都是很大的消耗。
对于copyOnwriteList的底层暂时研究到这里,粗略研究,欢迎讨论。

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值