1. 程式人生 > >2.8.2 並發下的ArrayList,以及源碼分析

2.8.2 並發下的ArrayList,以及源碼分析

blog util join() explicit ted cep ole 問題: port

package 第二章.並發下的ArrayList;

import java.util.ArrayList;
import java.util.List;

/**
* Created by zzq on 2018/1/19.
*/
public class 並發下的ArrayList {
static ArrayList<Integer> list=new ArrayList<Integer>();
public static class AddThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
list.add(i);
}
}

}
public static void main(String[] args) throws InterruptedException {
//ArrayList 網上介紹有容量上限 大約8GB左右
//擴容的公式 ((舊容量 * 3) / 2) + 1 註:這點與C#語言是不同的,C#當中的算法很簡單,是翻倍。
/* 一旦容量發生變化,就要帶來額外的內存開銷,和時間上的開銷。
所以,在已經知道容量大小的情況下,推薦使用下面方式進行聲明:
List arrayList = new ArrayList(CAPACITY_SIZE);
即指定默認容量大小的方式。
*/
Thread t1=new Thread(new AddThread());
Thread t2=new Thread(new AddThread());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(list.size());
/*上面的程序經過我多次測試得出3個結果
1:size為200000(理想情況)
2:size為小於200000(多次) 因為ArrayList 是一個線程不安全的容器
3:會報異常(這是由於ArrayList在擴容的時候,內部一致性被破壞了,由於沒有所的保護,
另一個線程訪問到了不一致的背部狀態,導致越界問題)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 2776
at java.util.ArrayList.add(ArrayList.java:441)
at 第二章.程序的幽靈之隱蔽的錯誤.並發下的ArrayList$AddThread.run(並發下的ArrayList.java:15)
at java.lang.Thread.run(Thread.java:745)
*/
}
}




1.ArrayListde add()方法:(源碼分析)
public boolean add(E e) {
@ 備註1: ensureCapacityInternal(size + 1); //
@ 備註2: elementData[size++] = e;
return true;
}
@ 備註1:解析 其中 ensureCapacityInternal(size + 1)方法是 比較當前list的長度和該list的容量,我們進入該方法繼續觀看

private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//判斷該list的容量 是否為空,如果為空,給它一個默認的值 10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

其中 ensureExplicitCapacity(minCapacity) 方法的作用 是判斷改list是否需要擴容:我們進入該方法中查看:

private void ensureExplicitCapacity(int minCapacity) {
modCount++;//位置加1

// overflow-conscious code
if (minCapacity - elementData.length > 0) //如果當前的size大於此刻list的容量,那麽就進行擴容。
grow(minCapacity);
}
其中 grow(minCapacity) 方法為擴容方法(請看源碼):

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//容量為原來的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//將此刻的list拷貝到一個新的擴容之後list中
}

@ 備註2:經過以上的判斷,然後 elementData[size++] = e; 將該元素加入到該list中

線程安全問題:

通過以上的分析,我們就知道,如果單線程中,是串行的,後面的操作是在之前的操作結束後再次執行,所以list的每一次add方法後都能保持list數據結構的一致

但是在多線程中:沒有用到鎖機制的前提下線程之間是並行的,那麽我們開始分析2種錯誤形式:(以我們的測試案例來說明)

第一種:size為小於200000(多次)

在 @ 備註1的操作時候2個線程同時拿到一個位置,然後添加,這就會造成 實際是添加2條數據,但是由於 index值是一樣的,那麽我們實際存入的是一條

第二種:會報異常(數組越界)

發生這種情況,是因為2個線程同時拿到了最後一個位置,但是他們都認為這是最後一個位置,所以list沒有進行擴容,所以在其中一個線程執行 @ 備註2 之後,另一個線程也執行了 @ 備註2

這就造成了,本來list的size為10,第二個線程添加的位置是11,所以就會報出 數組越界

2.8.2 並發下的ArrayList,以及源碼分析