非線程安全 | 線程安全 |
---|---|
ArrayList LinkedList |
Vector |
HashMap |
HashTable |
StringBuilder |
StringBuffer |
區別
容器類線程安全, 非線程安全的區別可以用下面這個例子來表述:
以ArrayList
和Vector
為例, 同時建立100個線程, 每個線程都向容器中添加100個元素,
最后統計容器內元素的數量, 對於ArrayList
來說, 最后的量並不一定是10000個, 甚至會出現IndexOutofBoundsException
, 但是對於Vector
來說, 最后的量一定是10000個, 且不會出現任何異常. 這便是線程安全與非線程安全的一個直觀表現.
非線程安全 != 多線程下不可使用
非線程安全並不意味着不可以在多線程環境下不可使用, 上述問題出現在多個線程操作同一個ArrayList
對象, 如果一個ArrayList
只在一個線程下進行操作, 還是可以使用ArrayList
的.
如何使非線程安全容器類變得線程安全
使用List<Object> list = Collections.synchronizedList(new ArrayList<Object>());
可以使list
變得線程安全.
造成非線程安全的原因
一般來說, 造成非線程安全主要有兩個原因:
1. 一個操作不是原子性操作
2. 執行過程中可能被中斷
查看ArrayList
關於add(E e)
的相關源碼:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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);
}
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
list中含有null的原因
即使不發生IndexOutofBoundsException
異常, 最后的元素總數也不全都是100000個.
問題出現在add(E e)
中的elementData[size++] = e;
, 這句代碼大致會分成以下兩步:
elementData[size] = e;
- `size++“
如果線程A執行完第1步中斷, 線程B開執行add, 執行到第1步時候因為size
還未+1, 所以線程B仍會將e賦值給elementData[size]
, 之后線程B執行+1操作, 線程A也執行+1操作, 也就意味着,並沒有對 elementData[size+1]
進行賦值, 其值也就為null.
元素總量不符合預期的原因
根本原因在於自加操作不是原子性的
線程B可能在線程A執行size++
中間就開始同時執行size++
, 這可能會使得線程A,B執行之初時size值相同, 導致元素總量小於預期.
IndexOutofBoundsException產生原因
ArrayList實際上也是一個數組, 只不過可以自動擴容, 出現IndexOutofBoundsException
說明在某些情況下, 還未擴容, 就添加元素進去了.
例如,線程A開始執行add()
, 執行到ensureExplicitCapacity(int minCapacity)
中的條件語句時, 如果此時添加的元素總數==數組的長度-1, 那么並不會執行擴容操作. 但是如果此時, 線程A中斷, 線程B開始執行, 此時由於線程A還未添加元素, 元素總數仍==數組的長度-1, 添加元素, 此時若線程A恢復, 開始執行添加元素, 由於此時元素總數==數組的長度, 再向其中添加元素就會拋出IndexOutofBoundsException
異常.
Vector
Vector
中關於add(E e)
的相關源碼
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}