ArrayList至非、Vector和Collections.synchronizedList()
ArrayList和Vector
前兩天看了ArrayList
的源碼抽减,然后想起Vector
來,常常會將兩者一起做對比速种。不過一想起Vector
馬上要被判死刑了根灯,就懶得去看Vector
的源碼,于是網(wǎng)上查了一下兩者的區(qū)別中符。
-
ArrayList
是線程不安全的姜胖,Vector
是線程安全的。 - 兩者擴容方式不同淀散。在底層數(shù)組容量不足時右莱,
ArrayList
會將容量擴容為原來的1.5倍。而Vector
支持在創(chuàng)建的時候主動聲明擴容時增加的容量的大小档插,通過Vector(int initialCapacity, int capacityIncrement)
構造函數(shù)實現(xiàn)慢蜓。如果沒有聲明,或者capacityIncrement <= 0
郭膛,那么默認擴容為原來的2倍晨抡。見下列代碼:
// Vector的擴容方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector和Collections.synchronizedList
好吧,其實今天不是要討論擴容方面的差異则剃,主要是要看一下線程安全方面的耘柱。
雖然是ArrayList
是線程不安全的,但是通過Collections.synchronizedList()
方法可以將線程不安全的List
轉成線程安全的List
棍现。但是呢调煎,在oracle的文檔里,有這么一句話:
If you need synchronization, a Vector will be slightly faster than an ArrayList synchronized with Collections.synchronizedList.
Vector
比Collections.synchronizedList
快一點點己肮?那這一點點到底是快在哪里呢士袄?我們看一下SynchronizedList
的代碼悲关。
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
...下面的代碼大多類似,就省略了
}
從代碼中可以看出娄柳,SynchronizedList<E>
類使用了委托(delegation)坚洽,實質上存儲還是使用了構造時傳進來的list
,只是將list
作為底層存儲西土,對它做了一層包裝讶舰。正是因為多了一層封裝,所以就會比直接操作數(shù)據(jù)的Vector
慢那么一點點需了。
從上面的代碼我們也可以看出來跳昼,SynchronizedList
的同步,使用的是synchronized
代碼塊對mutex
對象加鎖肋乍,這個mutex
對象還能夠通過構造函數(shù)傳進來鹅颊,也就是說我們可以指定鎖定的對象。而Vector
則使用了synchronized
方法墓造,同步方法的作用范圍是整個方法堪伍,所以沒辦法對同步進行細粒度的控制。而且同步方法加鎖的是this
對象觅闽,沒辦法控制鎖定的對象帝雇。這也是vector
和SynchronizedList
的一個區(qū)別。
線程安全并不"安全"
可能有些同學有在多線程環(huán)境下使用List
的需求蛉拙,所以選擇了Vector
或者Collections.SynchronizedList
尸闸,然后就以為可以再多線程環(huán)境下安全地操作List
了。但是這種想法可能會導致代碼出現(xiàn)不可預料的錯誤孕锄,因為雖然Vector
(以Vector為例)實現(xiàn)了各個方法操作的線程安全吮廉,但是當多個方法之間進行協(xié)作時,卻依然會出現(xiàn)race condition畸肆。
比如if(!list.contains(o)) list.add(o);
宦芦,還有Collections.swap(list, i, j);
,如果不在外部手工加鎖的話轴脐,多線程環(huán)境下调卑,這都會出現(xiàn)問題。尤其是對于List
經(jīng)常會使用到的迭代豁辉×钜埃看一下下面這段代碼:
public static void main(String[] args) throws InterruptedException {
Vector<Integer> vector = new Vector<>();
// 先存放1000個值讓iterator有值可以遍歷
for (int i = 0; i < 1000; i++) {
vector.add(i);
}
Thread iteratorThread = new Thread(new IteratorRunnable(vector));
iteratorThread.start();
// 主線程休眠5秒舀患,讓iteratorThread能夠充分跑起來徽级。這段時間是不會有問題的。
TimeUnit.SECONDS.sleep(5);
// 該線程啟動之后聊浅,會結構化修改Vector餐抢,然后就會拋出ConcurrentModificationException異常
Thread modifyVectorThread = new Thread(new ModifyVectorRunnable(vector));
modifyVectorThread.start();
}
/**
* 這個Runnable會不斷使用迭代器(for-each語句)遍歷Vector
*/
private static class IteratorRunnable implements Runnable {
private Vector<Integer> vector;
public IteratorRunnable(Vector<Integer> vector) {
this.vector = vector;
}
@Override
public void run() {
while(true) {
for (Integer i : vector) {
}
}
}
}
/**
* 這個Runnable會不斷添加新元素现使,也就是會結構化修改Vector
*/
private static class ModifyVectorRunnable implements Runnable {
private Vector<Integer> vector;
public ModifyVectorRunnable(Vector<Integer> vector) {
this.vector = vector;
}
@Override
public void run() {
while(true) {
vector.add(1);
}
}
}
IteratorRunnable
用來模擬迭代Vector
的線程,ModifyVectorRunnable
用來模擬結構化修改Vector
的線程旷痕。在main
函數(shù)中碳锈,iteratorThread
首先開始運行,不斷迭代Vector
的值欺抗。主線程休眠5s售碳,在這5s內(nèi),iteratorThread
是沒有問題的绞呈。5s過后贸人,modifyVectorThread
開始運行,該線程會向Vector
內(nèi)添加元素佃声,也就是結構化修改Vector
艺智。
有些同學可能覺得這段代碼不會有問題,因為Vector
是線程安全的圾亏,在多線程環(huán)境下理應正常運行十拣。但是這個線程安全是有缺陷的,再迭代的情況下志鹃,我們需要的實際上是對整個迭代過程加鎖夭问,而不是對迭代器的hasNext
、next
等單獨的方法加鎖曹铃。這段代碼會報ConcurrentModificationException
異常甲喝。如圖:
解決方案很簡單,對IteratorRunnable
的迭代過程加鎖就可以了:
public void run() {
while(true) {
// 對迭代過程加鎖
synchronized (vector) {
for (Integer i : vector) {
}
}
}
}