一:前言
相信好多小伙伴在面试的时候被问到: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是线程不安全的。