關於ArrayList的越界問題?

你們都知道 ArrayList是自動擴容的。 那爲何會存在越界問題?  java

話很少說 上代碼數組

 1 package test;  2 
 3 import java.util.ArrayList;  4 
 5 public class ThreadUnSafe {  6     public  static ArrayList<Integer> numberList= new ArrayList<Integer>();  7     public  static  class  addToList implements Runnable{  8         int startNum=0;  9         public  addToList(int startNum){ 10             this.startNum=startNum; 11  } 12  @Override 13         public  void run(){ 14             int count=0; 15             while (count<50){ 16                 try{ 17                     Thread.sleep(100); 18                 }catch (InterruptedException e){ 19  e.printStackTrace(); 20  } 21  numberList.add(startNum); 22                 System.out.println(Thread.currentThread().getName()+"=="+"第"+(count+1)+"次進入,添加的數子爲"+numberList.get(numberList.size()-1)+"---此時集合大小爲:"+numberList.size()); 23                 startNum+=2; 24                 count++; 25  } 26  } 27  } 28 
29     public static void main(String[] args) throws InterruptedException{ 30         Thread t1=new Thread(new addToList(0)); 31         Thread t2=new Thread(new addToList(1)); 32  t1.start(); 33  t2.start(); 34 
35  } 36 
37 }

測試結果:安全

Thread-1==第1次進入,添加的數字爲1---此時集合大小爲:1
Thread-0==第1次進入,添加的數字爲1---此時集合大小爲:1
Thread-0==第2次進入,添加的數字爲1---此時集合大小爲:2
Thread-1==第2次進入,添加的數字爲2---此時集合大小爲:3
Thread-0==第3次進入,添加的數字爲2---此時集合大小爲:4
Thread-1==第3次進入,添加的數字爲3---此時集合大小爲:5
Thread-0==第4次進入,添加的數字爲4---此時集合大小爲:7
Thread-1==第4次進入,添加的數字爲4---此時集合大小爲:7
Thread-0==第5次進入,添加的數字爲4---此時集合大小爲:8
Thread-1==第5次進入,添加的數字爲5---此時集合大小爲:9
Thread-1==第6次進入,添加的數字爲6---此時集合大小爲:10
Thread-0==第6次進入,添加的數字爲6---此時集合大小爲:10
Thread-0==第7次進入,添加的數字爲6---此時集合大小爲:11
Thread-1==第7次進入,添加的數字爲7---此時集合大小爲:12
Thread-1==第8次進入,添加的數字爲8---此時集合大小爲:14
Thread-0==第8次進入,添加的數字爲8---此時集合大小爲:14
Thread-1==第9次進入,添加的數字爲9---此時集合大小爲:15
Thread-0==第9次進入,添加的數字爲8---此時集合大小爲:16
Thread-1==第10次進入,添加的數字爲10---此時集合大小爲:17
Thread-0==第10次進入,添加的數字爲9---此時集合大小爲:18
Thread-0==第11次進入,添加的數字爲10---此時集合大小爲:19
Thread-1==第11次進入,添加的數字爲11---此時集合大小爲:20
Thread-0==第12次進入,添加的數字爲11---此時集合大小爲:21
Thread-1==第12次進入,添加的數字爲11---此時集合大小爲:21
Thread-1==第13次進入,添加的數字爲13---此時集合大小爲:22
Thread-0==第13次進入,添加的數字爲12---此時集合大小爲:23
Thread-1==第14次進入,添加的數字爲14---此時集合大小爲:25
Thread-0==第14次進入,添加的數字爲14---此時集合大小爲:25
Thread-1==第15次進入,添加的數字爲15---此時集合大小爲:26
Thread-0==第15次進入,添加的數字爲15---此時集合大小爲:26
Thread-0==第16次進入,添加的數字爲15---此時集合大小爲:28
Thread-1==第16次進入,添加的數字爲15---此時集合大小爲:28
Thread-1==第17次進入,添加的數字爲16---此時集合大小爲:29
Thread-0==第17次進入,添加的數字爲16---此時集合大小爲:29
Thread-0==第18次進入,添加的數字爲18---此時集合大小爲:31
Thread-1==第18次進入,添加的數字爲18---此時集合大小爲:31
Thread-0==第19次進入,添加的數字爲18---此時集合大小爲:32
Thread-1==第19次進入,添加的數字爲18---此時集合大小爲:32
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 33
at java.util.ArrayList.elementData(ArrayList.java:422)
at java.util.ArrayList.get(ArrayList.java:435)
at test.ThreadUnSafe$addToList.run(ThreadUnSafe.java:22)
at java.lang.Thread.run(Thread.java:748)
java.lang.ArrayIndexOutOfBoundsException: 33
at java.util.ArrayList.add(ArrayList.java:463)
at test.ThreadUnSafe$addToList.run(ThreadUnSafe.java:21)
at java.lang.Thread.run(Thread.java:748)多線程

?  爲何會有數組越界呢 。對於ArrayList而言,它實現List接口、底層使用數組保存全部元素。其操做基本上是對數組的操做。ide

其中:at java.util.ArrayList.add(ArrayList.java:463)的源代碼函數

public boolean add(E e) { ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e; return true; }
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
 
若是數組長度小於默認的容量10,則調用擴大數組大小的方法grow()。

其中 函數grow()解釋了基於數組的ArrayList是如何擴容的。數組進行擴容時,會將老數組中的元素從新拷貝一份到新的數組中
每次數組容量的增加大約是其原容量的1.5倍。函數體中,modCount是數組發生size更改的次數。而後if判斷,
接下來回到Add()函數,繼續執行,elementData[size++] = e; 這行代碼就是問題所在,當添加一個元素的時候,它可能會有兩步來完成:
1. 在 elementData[Size] 的位置存放此元素;
2. 增大 Size 的值。

在單線程運行的狀況下,若是 Size = 0,添加一個元素後,此元素在位置 0,並且 Size=1;

    若是是在多線程狀況下,好比有兩個線程,線程 A 先將元素存放在位置 0。可是此時 CPU 調度線程A暫停,線程 B 獲得運行的機會。線程B也向此 ArrayList 添加元素,由於此時 Size 仍然等於 0 (注意哦,咱們假設的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),因此線程B也將元素存放在位置0。而後線程A和線程B都繼續運行,都增長 Size 的值。那好,咱們來看看 ArrayList 的狀況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是「線程不安全」了測試

我猜測是,因爲沒有該方法沒有同步,致使出現這樣一種現象,用第一次異常,即下標爲15時的異常舉例。當集合中已經添加了14個元素時,一個線程率先進入add()方法,在執行ensureCapacityInternal(size + 1)時,發現還能夠添加一個元素,故數組沒有擴容,但隨後該線程被阻塞在此處。接着另外一線程進入add()方法,執行ensureCapacityInternal(size + 1),因爲前一個線程並無添加元素,故size依然爲14,依然不須要擴容,因此該線程就開始添加元素,使得size++,變爲15,數組已經滿了。而剛剛阻塞在elementData[size++] = e;語句以前的線程開始執行,它要在集合中添加第16個元素,而數組容量只有15個,因此就發生了數組下標越界異常!this

相關文章
相關標籤/搜索