關於ArrayList的越界問題?


大家都知道 ArrayList是自動擴容的。 那為什么會存在越界問題?  

話不多說 上代碼

 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接口、底層使用數組保存所有元素。其操作基本上是對數組的操作。

其中: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);
}
 
 函數體中,modCount是數組發生size更改的次數。然后if判斷,如果數組長度小於默認的容量10,則調用擴大數組大小的方法grow()。

其中 函數grow()解釋了基於數組的ArrayList是如何擴容的。數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中
每次數組容量的增長大約是其原容量的1.5倍。
 接下來回到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個,所以就發生了數組下標越界異常!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM