ArrayList源碼和多線程安全問題分析

1.ArrayList源碼和多線程安全問題分析

在分析ArrayList線程安全問題以前,咱們線對此類的源碼進行分析,找出可能出現線程安全問題的地方,而後代碼進行驗證和分析。java

1.1 數據結構

ArrayList內部是使用數組保存元素的,數據定義以下:數組

transient Object[] elementData; // non-private to simplify nested class access

在ArrayList中此數組便是共享資源,當多線程對此數據進行操做的時候若是不進行同步控制,即有可能會出現線程安全問題。安全

1.2 add方法可能出現的問題分析

首先咱們看一下add的源碼以下:數據結構

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

此方法中有兩個操做,一個是數組容量檢查,另外就是將元素放入數據中。咱們先看第二個簡單的開始分析,當多個線程執行順序以下所示的時候,會出現最終數據元素個數小於指望值。 多線程

按照此順序執行完以後,咱們能夠看到,elementData[n]的只被設置了兩次,第二個線程設置的值將前一個覆蓋,最後size=n+1。下面使用代碼進行驗證此問題。併發

1.3 代碼驗證

首先先看下如下代碼,開啓1000個線程,同時調用ArrayList的add方法,每一個線程向ArrayList中添加100個數字,若是程序正常執行的狀況下應該是輸出:ide

list size is :10000

代碼以下:spa

private static List<Integer> list = new ArrayList<Integer>();

    private static ExecutorService executorService = Executors.newFixedThreadPool(1000);

    private static class IncreaseTask extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
            for(int i =0; i < 100; i++){
                list.add(i);
            }
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
        }
    }

    public static void main(String[] args){
        for(int i=0; i < 1000; i++){
            executorService.submit(new IncreaseTask());
        }
        executorService.shutdown();
        while (!executorService.isTerminated()){
            try {
                Thread.sleep(1000*10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("All task finished!");
        System.out.println("list size is :" + list.size());
    }

當執行此main方法後,輸出以下: 線程

從以上執行結果來看,最後輸出的結果會小於咱們的指望值。即當多線程調用add方法的時候會出現元素覆蓋的問題。code

1.4 數組容量檢測的併發問題

在add方法源碼中,咱們看到在每次添加元素以前都會有一次數組容量的檢測,add中調用此方法的源碼以下:

ensureCapacityInternal(size + 1);

容量檢測的相關源碼以下:

private void ensureCapacityInternal(int minCapacity) {
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
       }

       ensureExplicitCapacity(minCapacity);
   }

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

容量檢測的流程圖以下所示: 圖片描述

咱們以兩個線程執行add操做來分析擴充容量可能會出現的併發問題: 當咱們新建一個ArrayList時候,此時內部數組容器的容量爲默認容量10,當咱們用兩個線程同時添加第10個元素的時候,若是出現如下執行順序,可能會拋出java.lang.ArrayIndexOutOfBoundsException異常。 圖片描述

第二個線程往數組中添加數據的時候因爲數組容量爲10,而此操做往index爲10的位置設置元素值,所以會拋出數組越界異常。

1.5 代碼驗證數組容量檢測的併發問題

使用以下代碼: private static List<Integer> list = new ArrayList<Integer>(3);

private static ExecutorService executorService = Executors.newFixedThreadPool(10000);

    private static class IncreaseTask extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
            for(int i =0; i < 1000000; i++){
                list.add(i);
            }
            System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
        }
    }

    public static void main(String[] args){

        new IncreaseTask().start();
        new IncreaseTask().start();
    }

執行main方法後,咱們能夠看到控制檯輸出以下: 圖片描述

1.6 ArrayList中其餘方法說明

ArrayList中其餘包含對共享變量操做的方法一樣會有併發安全問題,只須要按照以上的分析方法分析便可。

相關文章
相關標籤/搜索