0
点赞
收藏
分享

微信扫一扫

为什么说ArrayList是线程不安全的?



转载本文章请标明作者​​《爱喝纯净水的南荣牧歌》​​


为什么说ArrayList是线程不安全的?_高并发

开始行动,你已经成功一半了,献给正在奋斗的我们


缘起

因为最近在深入研究java高并发这块,所以在多线程场景下作了对ArrayList的测试,发现在高并发添加数据下,ArrayList会暴露三个问题;


  • 部分值为null(我们并没有add null进去)
  • 索引越界异常
  • size与我们add的数量不符

解决

为了知道这三种情况是怎么发生的,我们打开ArrayList的源码看一下。

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

大体可以分为三步:


  • 判断数组需不需要扩容,如果需要的话,调用grow方法进行扩容;
  • 将数组的size位置设置值(因为数组的下标是从0开始的);
  • 将当前集合的大小加1

下面我们来分析三种情况都是如何产生的:


  • 部分值为null: 当线程1走到了扩容那里发现当前size是9,而数组容量是10,所以不用扩容,这时候cpu让出执行权,线程2也进来了,发现size是9,而数组容量是10,所以不用扩容,这时候线程1继续执行,将数组下标索引为9的位置set值了,还没有来得及执行size++,这时候线程2也来执行了,又把数组下标索引为9的位置set了一遍,这时候两个先后进行size++,导致下标索引10的地方就为null了。
  • 索引越界异常: 线程1走到扩容那里发现当前size是9,数组容量是10不用扩容,cpu让出执行权,线程2也发现不用扩容,这时候数组的容量就是10,而线程1 set完之后size++,这时候线程2再进来size就是10,数组的大小只有10,而你要设置下标索引为10的就会越界(数组的下标索引从0开始);
  • size与我们add的数量不符:这个基本上每次都会发生,这个理解起来也很简单,因为size++本身就不是原子操作,可以分为三步:获取size的值,将size的值加1,将新的size值覆盖掉原来的,线程1和线程2拿到一样的size值加完了同时覆盖,就会导致一次没有加上,所以肯定不会与我们add的数量保持一致的;


举报

相关推荐

0 条评论