ArrayList自动扩容与线程的安全

ArrayList介绍

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    transient Object[] elementData; 

    private int size;

ArrayList维护了两个数组DEFAULT_CAPACITY和elementData。DEFAULT_CAPACITY是一个空数组,当创建一个空的ArrayList的时候就会使用DEFAULT_CAPACITY,public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; }可以看出在这里创建出ArrayList的时候容量是为0的(并不是10嗷),当在容器中添加一个元素以后,则会使用elementData来存储数据。只要关注好elementData数组就好啦。

扩容机制
这里就主要介绍ArrayList的扩容机制啦,当调用到add和addAll和readObject就有可能进行扩容操作,这里就用add方法举例(只添加一个元素,其他同理)

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

当往集合添加一个元素时就会将添加后所需要的容量大小进行判断,也就是方法ensureCapacityInternal啦!

 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

注意这里,前面说的当你创建的是一个空ArrayList的时候,就会将你的容量变为DEFAULT_CAPACITY(10),或者当你往空集合里加入10个以上的元素时,集合容量将会变成你加入的元素个数。之后就是ensureExplicitCapacity方法啦!

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

当你的元素所需最小容量已经大于elementData的数组最大长度时就需要进行扩容操作啦,也就是grow方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }

首先就给容量进行1.5倍扩容后赋值给newCapacity ,扩容1.5倍还不够的话就直接用你所需的容量咯!要是容量快达到int最大大小(2^31-1)的话就进入到这个方法hugeCapacity。

  private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

线程不安全实例
知道ArrayList是自动扩容的之后,按理说是不大会出现数组越界这个问题啦。但事实不是。下面就举个小例子来测试一下。代码如下

public class ArraryListThreadTest {
	static ArrayList list = new ArrayList<>();

	static class AddToList implements Runnable {
		int num = 0;

		public void run() {
			while (true) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				list.add(num);

				System.out.println(
						Thread.currentThread().getName() + "  add num is " + num + "  list'size is " + list.size());
				num += 2;
			}
		}
	}

	public static void main(String[] args) {
		Thread t1 = new Thread(new AddToList());
		Thread t2 = new Thread(new AddToList());
		t1.start();
		t2.start();
	}
}

输出结果为:

Thread-0  add num is 0  list'size is 2
Thread-1  add num is 0  list'size is 2
Thread-1  add num is 2  list'size is 4
Thread-0  add num is 2  list'size is 4
Thread-1  add num is 4  list'size is 6
Thread-0  add num is 4  list'size is 6
Thread-0  add num is 6  list'size is 8
Thread-1  add num is 6  list'size is 8
Thread-1  add num is 8  list'size is 10
Thread-0  add num is 8  list'size is 10
Thread-1  add num is 10  list'size is 11
Thread-0  add num is 10  list'size is 11
Thread-0  add num is 12  list'size is 13
Thread-1  add num is 12  list'size is 13
Thread-1  add num is 14  list'size is 14
Thread-0  add num is 14  list'size is 14
Thread-1  add num is 16  list'size is 15
Thread-0  add num is 16  list'size is 15
Thread-0  add num is 18  list'size is 17
Thread-1  add num is 18  list'size is 17
Thread-0  add num is 20  list'size is 19
Thread-1  add num is 20  list'size is 19
Thread-0  add num is 22  list'size is 20
Thread-1  add num is 22  list'size is 20
Thread-1  add num is 24  list'size is 21
Thread-0  add num is 24  list'size is 21
Thread-1  add num is 26  list'size is 23
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 22
	at java.util.ArrayList.add(ArrayList.java:444)
	at conllection.ArraryListThreadTest$AddToList.run(ArraryListThreadTest.java:19)
	at java.lang.Thread.run(Thread.java:745)

为什么在elementData长度22的时候出现数组越界异常呢?

众所周知ArrayList是线程不安全的嘛(没实现Serializable就是惨。。。。)

由于没有该方法没有同步,导致出现这样一种现象,用下标为22时的异常举例。当集合中已经添加了21个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,故size依然为21,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为22,数组已经满了。而刚刚阻塞在elementData[size++] = e;语句之前的线程开始执行,它要在集合中添加第23个元素,而数组容量只有22个,所以就发生了数组下标越界异常!

关于ArrayList的越界问题参考文章https://www.cnblogs.com/smellpawn/p/10841480.html

你可能感兴趣的:(源码解读)