ArrayList自動擴容與線程的安全

ArrayList介紹

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, 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<Integer> 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章