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());
}
????結(jié)果:

發(fā)現(xiàn)缺少了兩條數(shù)據(jù),插入數(shù)據(jù)總個(gè)數(shù)10000,結(jié)果遍歷只顯示有9998條數(shù)據(jù)
不安全原因

????查看源代碼可以了解到ArrayList插入數(shù)據(jù)的方法分兩步,第一步是擴(kuò)容集合長度,第二步放入數(shù)據(jù)。當(dāng)我們多個(gè)線程同時(shí)進(jìn)行插入數(shù)據(jù)的操作時(shí),某個(gè)線程執(zhí)行了第一步,擴(kuò)大了集合大小,同時(shí)另一個(gè)線程正好也執(zhí)行了這一步,又?jǐn)U大了一級(jí),此時(shí)會(huì)產(chǎn)生新的數(shù)組對(duì)象,最后兩個(gè)同時(shí)執(zhí)行賦值的時(shí)候,賦值位置正好都在size上,那么此時(shí)會(huì)發(fā)現(xiàn)在一個(gè)位置,而兩次擴(kuò)容生成的對(duì)象不同,所以后執(zhí)行賦值的會(huì)把之前的覆蓋掉。
解決辦法
- List<String> list = Collections.synchronizedList(new ArrayList<>());
- 使用其他安全的來代替
ArrayList源碼分析
????ArrayList實(shí)際上就是對(duì)數(shù)組進(jìn)行不斷的擴(kuò)容,初始默認(rèn)長度為10。

????每次執(zhí)行add方法,相當(dāng)于為數(shù)組對(duì)應(yīng)位置賦值,有一個(gè)全局變量SIZE,初始化為0,每次賦值都為size位置上賦值,之后size加1。
????ArrayList實(shí)例化的時(shí)候允許我們傳入初始化數(shù)組大小,默認(rèn)是10,。所以當(dāng)我們知道要插入數(shù)據(jù)的總個(gè)數(shù)時(shí)候,可以在初始化的時(shí)候直接定義list的大小,這樣可以防止每次插入數(shù)據(jù)的時(shí)候總會(huì)復(fù)制數(shù)組浪費(fèi)資源。

????執(zhí)行add方法時(shí)候,分兩步,第一步判斷是否數(shù)組需要擴(kuò)容第二步插入數(shù)據(jù)。其中第一步會(huì)執(zhí)行到grow方法,在該方法中進(jìn)行數(shù)組的擴(kuò)容。

????在執(zhí)行g(shù)row進(jìn)行數(shù)組擴(kuò)容之前,還會(huì)先經(jīng)歷三個(gè)ensureCapacityInternal 來確定是否需要擴(kuò)容,以及擴(kuò)容多少,按照現(xiàn)在它的算法,每次擴(kuò)容會(huì)擴(kuò)原數(shù)組一半大小,即原長度為9,則擴(kuò)容后為9+4=13。

- arraylist 是可以查找第一個(gè)null的位置的,請(qǐng)看源碼
