关于subList导致的ConcurrentModificationException异常分析

由于某老项目中使用的 BRVAH 为2x版本,所以迁移AndroidX后需要将其一起迁移至3x版本。迁移完毕后运行,发现某页面抛出ConcurrentModificationException,在此分析记录一下。
1、异常信息
    java.util.ConcurrentModificationException
        at java.util.ArrayList$SubList.size(ArrayList.java:1057)
        at java.util.AbstractCollection.isEmpty(AbstractCollection.java:86)
        at com.chad.library.adapter.base.BaseQuickAdapter.setList(BaseQuickAdapter.kt:1186)
		at ......

此异常归根结底是由于在ArrayList的基类AbstractList中存在一个变量值modCount,顾名思义是用来记录修改次数的。当集合存在并发修改时可能会导致该值异常,从而抛出,本文主要分析由于subList导致该异常抛出的情况。

2、业务代码分析

先来看一下业务代码内容(示例代码),大致含义为从adapter中取出内容,判断如果不为空,则截取最后一个item重新赋值。

    private void test() {
        List<String> data = mAdapter.getData();
        if (!data.isEmpty()) {
            List<String> subList = data.subList(data.size() - 1, data.size());
            mAdapter.setList(subList);
        }
    }

再来看一下3x版本的BRVAH中BaseQuickAdapter#setList的方法;

    open fun setList(list: Collection<T>?) {
        if (list !== this.data) {
            this.data.clear()
            if (!list.isNullOrEmpty()) {
                this.data.addAll(list)
            }
        } else {
			...
        }
        ...
    }

此处留意三四行的clearisNullOrEmpty方法,下面提及;
其中log显示异常抛出是在第四行的isNullOrEmpty方法中,最终会调用集合的size方法;此处注意一下,我们在业务代码中最终setList的是subList之后的一个对象,我们先看一下ArrayList#subList方法和其返回的对象

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }
private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

其返回了一个SubList对象,该类直接继承AbstractList类,也是Collection的子类。在构造方法内,其还存储了ArrayList作为自己的parent,并记录了parentmodCount
根据上面BRVAH的check方法,我们就知道我们看的是SubList对象内的size方法,而不是ArrayListsize方法;

    public int size() {
        if (ArrayList.this.modCount != this.modCount)
            throw new ConcurrentModificationException();
        return this.size;
    }

至此我们找到了抛出异常的具体方法;
那么我们来看一下为何SubListmodCount会与其parentmodCount不一致。重新看一下BRVAH的setList方法,我们发现在检查是否为空之前,调用了其内部成员变量dataclear方法,此处的clear即为我们SubListparent由于我们的业务代码中,用来切割的原集合就是从adapter中取出的),我们来看一下ArrayList#clear方法;

    public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

clear方法内部可以看到,对于modCount进行了自增操作,所以导致了SubListmodCount与其parent的modCount不一致。
至此,我们就知道了为何会导致该异常抛出了。

3、扩展思考

崩溃发生后,我在项目中查找运用了subList方法的地方,发现了另一个场景,同样是使用了subList之后调用setList,却没有崩溃,继续使用示例代码分析一下;

导致崩溃
    private void test() {
        initAdapter();
        mAdapter.addData(mLocalArr);
        List<String> data = mAdapter.getData();
        if (!data.isEmpty()) {
            List<String> subList = data.subList(data.size() - 1, data.size());
            mAdapter.setList(subList);
        }
    }
正常运行
    private void test() {
        initAdapter();
        mAdapter.addData(mLocalArr);
        
        List<String> data = mLocalArr;
        
        if (!data.isEmpty()) {
            List<String> subList = data.subList(data.size() - 1, data.size());
            mAdapter.setList(subList);
        }
    }

总结一下,即3x版本中的setList方法中,不会使用你传入的对象存储为mData,而是将传入的内容取出addAll。第一种写法中,我们从adapter中取出集合再进行操作,最终再setList,相当于我们对同一个对象先进行了subListclear,就会导致同步操作集合的异常。

简化一下整体崩溃代码流程,即:

    private void mackCrash(){
        List<String> a = new ArrayList<>();
        a.add("1");
        a.add("2");
        //先对a集合进行切割
        List<String> b = a.subList(0, 1);
        //而后对a集合进行clear
        a.clear();
        //最终校验对b集合进行校验
        boolean empty = b.isEmpty();
    }

最终在调用b集合的isEmpty方法时抛出异常。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值