源码分析为什么ArrayList是线程不安全的

一:前言

相信好多小伙伴在面试的时候被问到:ArrayList是线程安全的吗?或者是ArrayList和LinkedList哪个是线程安全的?当你二选一的时候,脑袋瓜子只会想着选一个,都不知道那就蒙一个吧。。。其实这本就是一个坑,他们的线程都是不安全的,这篇文章就从源码的角度来分析一下为什么ArrayList是线程不安全的。话不多说,上源码!

二:源码分析

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
  

    /**
     * 初始容量是10
     */
    private static final int DEFAULT_CAPACITY = 10;

     /**
     * 定义一个数组 用来存储要添加的数据
     */
    transient Object[] elementData; 
     /**
     * 顾名思义:列表大小,elementData中元素的个数
     */
    private int size;
}


===================================================
/**
* 添加方法add
*/
public boolean add(E e) {
        ensureCapacityInternal(size + 1); 
        elementData[size++] = e;
        return true;
    }



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

综合解释一下:

前面几个字段有相应的注释,简单明了 说一下这个add方法其中的原理:

ensureCapacityInternal(); 就是将当前要添加的元素追加的列表的后面,判断列表的 elementData 数组的大小是否满足,size+1其实就是给元素个数加1,但是这里会遇到一个问题如果说size+1大于了自身已有的容量,那么就会自动扩容。

elementData[size++] = e;这样的操作本身就有问题,他不是原子性的操作,我们把它拆分开来是两个表达式:elementData[size]=e; size=size+1;elementData与size都是全局变量,但没有进行sychronization同步处理,如果是遇到多线程的问题,很容易出现值被覆盖的问题。

问题重现:

elementData[size]=e;解释:

开始size=0;当线程A添加元素的时候,他发现size=0,他就把值放到了elementData 下标为0的位置上,此时线程B也在添加元素 他同时发现size=0,他也会把值放到elementData下标为0的位置上

size=size+1

线程A继续执行size+1,当前size为1;

线程B继续执行size+1,当前size为2;

结果:你就会发现elementData下标为0的数据由线程B覆盖了线程A,但是elementData下标为1的数据是null,而此时的size是2,所以说这就出现了线程不安全的问题。

总结来说:就是一个ArrayList在添加元素的时候,会分为两个步骤,

1.在相应的位置上添加元素

2.增大size的值

都分为两个步骤了,在多线程的时候,如果线程A仅仅执行了一步但是线程A暂停了,此时线程B在添加元素的时候,又开始按照最开始的情况添加元素,此时就会有冲突了,所以说ArrayList是线程不安全的。

经验分享 程序员 微信小程序 职场和发展