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方法的时候会出现元素覆盖的问题。

 

posted @ 2017-09-18 01:02  myseries  阅读(1263)  评论(0编辑  收藏  举报