java.util(ArrayList)
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final int DEFAULT_CAPACITY = 10; // ArrayList中Object[]中的默认初始容量 private static final Object[] EMPTY_ELEMENTDATA = {}; //空Object[]数组对象 private transient Object[] elementData; //定义了一个私有的未被序列化的数组elementData,用来存储ArrayList的对象列表 private int size; //ArrayList中实际数据的数量 public ArrayList(int initialCapacity) { //带容量的构造函数 super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { //无参构造函数,默认容量为10,这里的1暂时还没有设置,在add(E)的时候会指定 super(); this.elementData = EMPTY_ELEMENTDATA; } public boolean add(E e) { // 确保数组容量足够添加元素进入数组 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //10->16->25->38->58->88->... if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);//意思就是把原数组内容复制到行数组上,容量变大了,将引用赋给elementData } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
以上是jdk1.7的描述,结论如下:
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长;
ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
注意扩充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组(详见下面的第3点)。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。
ArrayList的数组扩容调用了Arrays.copyof(),该方法实际上是在其内部又创建了一个长度为newlength的数组,调用System.arraycopy()方法,将原来数组中的元素复制到了新的数组中。System.arraycopy()方法。该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。
多线程安全问题分析
private transient Object[] elementData; //定义了一个私有的未被序列化的数组elementData,用来存储ArrayList的对象列表
ArrayList内部是使用数组保存元素的,在ArrayList中此数组即是共享资源,当多线程对此数据进行操作的时候如果不进行同步控制,即有可能会出现线程安全问题。
一: add方法可能出现的问题分析
首先我们看一下add的源码如下:
public boolean add(E e) { ensureCapacityInternal(size + 1); // 是否需要扩容 elementData[size++] = e;//赋值 return true; }
1:下标越界问题
多个线程进入ensureCapacityInternal()并执行完毕,此时都不需要扩容,依次赋值时会size+1,所以从第二个开始的线程赋值时其下标很可能超过了容量值,赋值时就报错了
2:存入的值变为null
elementData[size++] = e是先赋值再size+1,多线程运行到赋值还没+1时,size位置上被覆盖了多次,然后多次+1,size+1,+2等位置没赋值过,下次就直接从size+n开始赋值,看起来就add了null值一样,此时不会报错,因为add时没有null所以取出时没做考虑就可能报NullPointerException了.
3.数据个数小于预期值
在多线程操作下 size++不是原子操作,会出现最终数据元素个数小于期望值。
代码验证
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test4 { private static List<Integer> list = new ArrayList<Integer>(); private static ExecutorService executorService = Executors.newFixedThreadPool(1000); private static class IncreaseTask extends Thread{ @Override public void run() { System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!"); for(int i =0; i < 100; i++){ list.add(i); } System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!"); } } public static void main(String[] args) { for(int i=0; i < 1000; i++){ executorService.submit(new IncreaseTask()); } executorService.shutdown(); while (!executorService.isTerminated()){ try { Thread.sleep(1000*10); }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println("All task finished!"); System.out.println("list size is :" + list.size()); } }
从以上执行结果来看,最后输出的结果会小于我们的期望值。即当多线程调用add方法的时候会出现元素覆盖的问题。