ArrayList线程不安全的个人源码分析

1、ArrayList线程不安全的三种结果
(1)某些信息没有添加成功(丢失了一些数据)
(2)下标越界
(3)Null

分析

1)丢失数据

先看ArrayList.add()的源码

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

可以得出ArrayList添加数据大体分为2步,第一步确定数组长度是否满足即需不需要扩容,第二步添加数据

这里假设2个线程,线程A,线程B
线程A:获取当前size为2,确定范围不需要扩容,执行 elementData[size++] = e;这里分为2大步第一步elementData[size] = e,第二步size++,size++又分为三步:(1)获取size的值,(2)size+1,(3)重新赋值给size;于是出现A执行完第一大步和第二大步的第一小步之后被挂起,线程B正常执行完add操作,此时A获取到cpu资源继续运行,执行第二步size++,但此前赋予的值却被线程B覆盖了,这就是丢失数据的原因

**(

2)下标越界问题

这里主要关注add方法的第一步
在JDK1.8中,ArrayList构造一个实例是不会初始化一个长度为10的数组的,而是一个叫做DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空数组,在第一次执行add方法时会初始化一个长度为10的数组,这里简单说一下流程(以下是讲解第一次执行add()方法初始化数组的过程)

第一次执行add()方法时,给ensureCapacityInternal()传递一个size+1的参数即minCapacity = 1

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

再到calculateCapacity(),这里首先判断了是否是一个为空的数组,若是则返回默认大小10和minCapacity中较大的数值

 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

在执行ensureExplicitCapacity(int minCapacity)这里的参数是ensureCapacityInternal()的返回值即10,modCount是记录修改的次数,判断minCapacity和当前数组的长度,若比当前数组大则需要扩容

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

再看grow(),首先定义一个newCapacity用于接收扩容后的长度,oldCapacity+(oldCapacity >> 1)等价于1.5倍的oldCapacity,因为oldCapacity为0,所以扩容后的值仍小于minCapacity=10,于是
newCapacity = minCapacity,创建了一个长度为newCapacity长度的数组,并由elementData指向。

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

下面讲一下多线程临界扩容存在的问题,假设2个线程A、B,当前的size为9。
线程A需要添加一个数,于是对size+1进行判定,发现并没有超过10于是不执行grow命令,此时A被挂起,线程B也要添加一个数据,对size+1进行判定,此时size仍等于9于是也不执行grow命令,并成功运行在size=10的位置添加了一个值,此时A开始继续运行,此时size已经等于10了,运行到elementData[size++] = e时就会出现下标越界的问题
(图是偷得)
偷个图

3)Null

其实和第一个数据丢失非常类似,因为size++分为三步(1)获取size的值,(2)size+1,(3)重新赋值给size。在数据丢失的情况下为执行了第一小步(1)获取size的值之后被挂起,而出现null值是因为线程A还没到获取size的值就被挂起了,线程B完整执行完后,在继续执行线程A,此时获取的size值为线程B修改过之后的值,线程A继续size+1,就会出现null值,所以null值得出现也必定会有数据覆盖

纯个人理解,欢迎互相交流!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值