ArrayList(線程不安全,源碼)

ArrayList線程不安全

不安全事例代碼

 public static void main(String[] args) {
        final ArrayList<Integer> arrayList = new ArrayList<>();
        for(int i=0;i<10000;i++){
            final int a = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    arrayList.add(a);
                    System.out.println(a);
                }
            }).start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0;i<arrayList.size();i++){
            System.out.println(arrayList.get(i));
        }
        System.out.println("================ size = " + arrayList.size());
    }

    結果:

在這裏插入圖片描述

    發現缺少了兩條數據,插入數據總個數10000,結果遍歷只顯示有9998條數據

不安全原因

在這裏插入圖片描述

    查看源代碼可以瞭解到ArrayList插入數據的方法分兩步,第一步是擴容集合長度,第二步放入數據。當我們多個線程同時進行插入數據的操作時,某個線程執行了第一步,擴大了集合大小,同時另一個線程正好也執行了這一步,又擴大了一級,此時會產生新的數組對象,最後兩個同時執行賦值的時候,賦值位置正好都在size上,那麼此時會發現在一個位置,而兩次擴容生成的對象不同,所以後執行賦值的會把之前的覆蓋掉。

解決辦法

  • List list = Collections.synchronizedList(new ArrayList<>());
  • 使用其他安全的來代替

ArrayList源碼分析

    ArrayList實際上就是對數組進行不斷的擴容,初始默認長度爲10。

在這裏插入圖片描述

    每次執行add方法,相當於爲數組對應位置賦值,有一個全局變量SIZE,初始化爲0,每次賦值都爲size位置上賦值,之後size加1。

    ArrayList實例化的時候允許我們傳入初始化數組大小,默認是10,。所以當我們知道要插入數據的總個數時候,可以在初始化的時候直接定義list的大小,這樣可以防止每次插入數據的時候總會複製數組浪費資源。
在這裏插入圖片描述

    執行add方法時候,分兩步,第一步判斷是否數組需要擴容第二步插入數據。其中第一步會執行到grow方法,在該方法中進行數組的擴容。

在這裏插入圖片描述

    在執行grow進行數組擴容之前,還會先經歷三個ensureCapacityInternal 來確定是否需要擴容,以及擴容多少,按照現在它的算法,每次擴容會擴原數組一半大小,即原長度爲9,則擴容後爲9+4=13。

在這裏插入圖片描述

    arraylist 是可以查找第一個null的位置的,請看源碼

在這裏插入圖片描述

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