1. 程式人生 > >ArrayList執行緒不安全詳解

ArrayList執行緒不安全詳解

首先需要了解什麼是執行緒安全:執行緒安全就是說多執行緒訪問同一程式碼(物件、變數等),不會產生不確定的結果。 既然說ArrayList是執行緒不安全的,那麼在多執行緒中操作一個ArrayList物件,則會出現不確定的結果。具體是怎樣不確定,請看測試下面這段程式碼(在此測試ArrayList的add方法):

public class ArrayListInThread implements Runnable{

    //執行緒不安全
    private List threadList = new ArrayList();
    //執行緒安全
    //private List threadList = Collections.synchronizedList(new ArrayList());

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //把當前執行緒名稱加入list中
        threadList.add(Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException{
        ArrayListInThread listThread = new ArrayListInThread();

        
        for(int i = 0; i < 100; i++){
            Thread thread = new Thread(listThread, String.valueOf(i));
            thread.start();
        }
        
        //等待子執行緒執行完
        Thread.sleep(2000);

        System.out.println(listThread.threadList.size());
        //輸出list中的值
        for(int i = 0; i < listThread.threadList.size(); i++){
            if(listThread.threadList.get(i) == null){
                System.out.println();;
            }
            System.out.print(listThread.threadList.get(i) + "  ");
        }
    }
}
執行幾次會發現結果不一樣,甚至有時會出現ArrayIndexOutOfBoundsException,貼出幾次執行的結果:

結果一:

結果二:

結果三:

以上執行結果說明ArrayList確實是執行緒不安全的,然後我們從執行緒併發的角度分析,為何會出現這樣的結果:

首先,我們先來看一下ArrayList中的add方法是如何實現的(檢視詳細原始碼請點選):

//新增元素e    
    public boolean add(E e) {    
        // 確定ArrayList的容量大小    
        ensureCapacity(size + 1);  // Increments modCount!!    
        // 新增e到ArrayList中    
        elementData[size++] = e;    
        return true;    
    }    
    
    // 確定ArrarList的容量。    
    // 若ArrayList的容量不足以容納當前的全部元素,設定 新的容量=“(原始容量x3)/2 + 1”    
    public void ensureCapacity(int minCapacity) {    
        // 將“修改統計數”+1,該變數主要是用來實現fail-fast機制的    
        modCount++;    
        int oldCapacity = elementData.length;    
        // 若當前容量不足以容納當前的元素個數,設定 新的容量=“(原始容量x3)/2 + 1”    
        if (minCapacity > oldCapacity) {    
            Object oldData[] = elementData;    
            int newCapacity = (oldCapacity * 3)/2 + 1;    
            //如果還不夠,則直接將minCapacity設定為當前容量  
            if (newCapacity < minCapacity)    
                newCapacity = minCapacity;    
            elementData = Arrays.copyOf(elementData, newCapacity);    
        }    
    }
結果中,有的值沒有出現(結果一中3沒有出現),有的出現了null值,這是由於賦值時出現了覆蓋。賦值語句為:elementData[size++] = e,這條語句可拆分為兩條: 1. elementData[size] = e; 2. size ++; 假設A執行緒執行完第一條語句時,CPU暫停執行A執行緒轉而去執行B執行緒,此時ArrayList的size並沒有加一,這時在ArrayList中B執行緒就會覆蓋掉A執行緒賦的值,而此時,A執行緒和B執行緒先後執行size++,便會出現值為null的情況;至於結果三中出現的ArrayIndexOutOfBoundsException異常, 則是A執行緒在執行ensureCapacity
(size+1)後沒有繼續執行,此時恰好minCapacity等於oldCapacity,B執行緒再去執行,同樣由於minCapacity等於oldCapacity,ArrayList並沒有增加長度,B執行緒可以繼續執行賦值(elementData[size] = e)並size ++也執行了,此時,CPU又去執行A執行緒的賦值操作,由於size值加了1,size值大於了ArrayList的最大長度,
因此便出現了ArrayIndexOutOfBoundsException異常。 既然ArrayList是執行緒不安全的,但如果需要在多執行緒中使用,可以採用list<Object> list =Collections.synchronizedList(new ArrayList<Object>)來建立一個ArrayList物件。