浅析JDK1.7 ArrayList源码。
写在开篇
ArrayList源码相比我们之前看过的Map系列的源码要简单一些,所以接下来我们按照原来的套路,简单分析一下ArrayList的源码吧。
类的继承关系
ArrayList继承AbstractList抽象类,实现了List(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)这几个接口。1
2public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
成员变量
1 | /** |
构造方法
ArrayList中有三个构造方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* 给定参数初始化数组的大小
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* 默认初始化一个空数组
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
/**
* 以实现了Collection接口的集合类,来初始化elementData
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
List接口的实现
add()
ArrayList中有两个add方法,第一个是默认在下标为size+1的位置添加元素。1
2
3
4
5
6
7public boolean add(E e) {
// 扩容检查
ensureCapacityInternal(size + 1);
// 数组末尾添加元素
elementData[size++] = e;
return true;
}
另一个add()则是在指定位置添加元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
/**
* 将旧数组拷贝到一个新数组中
* @param elementData 被复制的原数组
* @param index 被复制数组的第几个元素开始复制
* @param elementData 复制的目标数组
* @param index + 1 从目标数组index + 1位置开始粘贴
* @param size - index 复制的元素个数
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将新元素赋给该下标
elementData[index] = element;
size++;
}
扩容
1 | private void ensureCapacityInternal(int minCapacity) { |
set()
1 | public E set(int index, E element) { |
get()
1 | public E get(int index) { |
remove()
同样地,有两个remove()方法。一个是删除下标为index的元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public E remove(int index) {
rangeCheck(index); // 检查下标
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1; // 需要移动的个数
if (numMoved > 0)
// 将下标为index+1后的元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将下标为size的元素置为空,同时将size-1
elementData[--size] = null;
return oldValue;
}
另一个是删除数组中相同的元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public boolean remove(Object o) {
if (o == null) { // 删除数组中的空元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else { // 输出数组中和o相同的元素
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 与上面讲的删除remove()过程相似
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
为什么是线程不安全的
主要有两个线程不安全的隐患,都出现在下面这段代码中:1
2
3
4
5
6
7public boolean add(E e) {
// 扩容检查
ensureCapacityInternal(size + 1);
// 数组末尾添加元素
elementData[size++] = e;
return true;
}
扩容检查时
假设现在有A、B两个线程在对数组进行插入元素操作,此时size=9(ArrayList 数组大小为默认的10)。
- 线程 A 进入add(),获取到size为9,调用ensureCapacityInternal()后判断不需要扩容,时间片消耗完,线程A挂起。
- 线程 B 开始执行,调用ensureCapacityInternal()后发现也不需要扩容。于是插入元素,且size自增1(size=10)。
- 线程 A 接着执行,尝试在下标为10的位置插入元素,此时就会抛出ArrayIndexOutOfBoundsException。
向数组末尾添加元素时
同样现在有两个线程A、B在对数组进行插入元素操作,size=0。
- 线程 A 执行完
elementData[size] = e;
后时间片耗尽,挂起。 - 线程 B 开始执行,由于size还是为0,所以在
elementData[size] = e;
时,会将线程 A 插入的元素覆盖掉。