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());
}
結果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmL0kzN1EjNwATMxADOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
發現缺少了兩條資料,插入資料總個數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的位置的,請看源碼