浅析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 插入的元素覆盖掉。
