ArrayList併發問題分析

併發問題老是感覺很棘手,這次碰到了一個ArrayList在線程池中add出現null數據的問題,雖然之前就知道ArrayList是非線程安全的,但是具體爲啥不安全,爲啥會出現空值,沒有深入去理解,這次出現這個問題,經過自己分析,基本知道了這類問題出錯會出在哪兒,對於這類問題的分析有點譜了

1.問題描述:

for循環線程池中啓10個任務進行list.add(),加完後,發現第一個值爲空,而且list的size也不是10

2.分析:

線程問題比較不好分析,主要是太抽象,都隱藏在後臺,不能直觀的觀察到,而且時有時無,所以比較難分析

1.第一步,出這種問題,首先確定,肯定是共享變量,非原子化操作引起的

ArrayList的add方法很簡單,就下面這兩句

ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;

第一句是擴容,可以排除(其實也有關係),直覺上應該是size++這個操作不是原子性的引起的

2.第二步,給自己插上想象的雞翅膀

2.1 size小的原因,我猜如下:

最後的幾個add,size++都在前一個線程沒有加完的基礎上++,導致最後的幾個對象都加到最後一個位置上

(有待推敲,因爲有擴容因素)

2.2 第一個爲空的原因,我猜如下:

最開始的爲空,第一個線程準備size++,第二個線程已經size++好了,導致兩個線程都加到了第二個位置上,

第一個沒人設置,所以爲空(也不太確定,都走到size++,放置的時候size已經+2了,但爲什麼中間沒有null的出現?)

好,分析完畢



可能是上面的原因,但是很多細節經不起推敲

比如這一步,如果是空的話,就會將size設置爲默認值10,但是實際有size爲9,或者8的情況

ensureCapacityInternal(size + 1);  // Increments modCount!!

說明了什麼?這一步沒有執行,直接到1,然後用它的算法size=size+size>>1進行擴容,

其實不是沒有執行是他自己有自定義的序列化方式,自己的size跟這個elementData的length無關

爲什麼沒執行?就算再怎麼亂序,也至少會有一個線程是從size=0開始執行的,只要是0就會默認將Arraylist copy進一個size爲10的新list裏

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//其中default_capacity爲10,第一次執行的時候minCapacity爲1,所以minCapacity爲10
    }

    ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity這裏執行的是下面的代碼:如果新長度大於arr的長度,就grow他
if (minCapacity - elementData.length > 0)
    grow(minCapacity);
grow代碼如下:拿到老size,將新size和老size的3/2進行比較,取大的將arr copy進去
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
後來發現arraylist有自己的序列化邏輯,所以裏面的arr通過fastjson的序列化方式打印出來可能不對,
size可能是錯的,它裏面的elementData是自定義的序列化,其實這段話沒有意義,跟序列化無關

第一個值爲空可能是因爲兩線程copy和賦值錯序了,第一個線程size++=e了,第二個線程copy將空的賦進去,把第一個置空了,但爲什麼只有第一個爲空?因爲之後的至少都有值,不會有把空copy進arraylist裏的情況


綜上所述,加粗的兩段話分別是arraylist在併發情況下size小和第一個爲空的原因,也是我瞎猜的,但估計也差不多



總結如下:

1.線程併發問題,首要就分析共享變量

2.object對象的序列化方法很重要

3.寫的太粗糙了,以後再改




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章