併發問題老是感覺很棘手,這次碰到了一個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);
size可能是錯的,它裏面的elementData是自定義的序列化,其實這段話沒有意義,跟序列化無關
第一個值爲空可能是因爲兩線程copy和賦值錯序了,第一個線程size++=e了,第二個線程copy將空的賦進去,把第一個置空了,但爲什麼只有第一個爲空?因爲之後的至少都有值,不會有把空copy進arraylist裏的情況
綜上所述,加粗的兩段話分別是arraylist在併發情況下size小和第一個爲空的原因,也是我瞎猜的,但估計也差不多
總結如下:
1.線程併發問題,首要就分析共享變量
2.object對象的序列化方法很重要
3.寫的太粗糙了,以後再改