天天看點

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());
    }
           

    結果:

ArrayList(線程不安全,源碼)

    發現缺少了兩條資料,插入資料總個數10000,結果周遊隻顯示有9998條資料

不安全原因

ArrayList(線程不安全,源碼)

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

解決辦法

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

ArrayList源碼分析

    ArrayList實際上就是對數組進行不斷的擴容,初始預設長度為10。

ArrayList(線程不安全,源碼)

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

    ArrayList執行個體化的時候允許我們傳入初始化數組大小,預設是10,。是以當我們知道要插入資料的總個數時候,可以在初始化的時候直接定義list的大小,這樣可以防止每次插入資料的時候總會複制數組浪費資源。

ArrayList(線程不安全,源碼)

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

ArrayList(線程不安全,源碼)

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

ArrayList(線程不安全,源碼)

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

ArrayList(線程不安全,源碼)