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的位置的,請看源碼