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)
構(gòu)造函數(shù)實現(xiàn)。如果沒有聲明,或者capacityIncrement <= 0
,那么默認(rèn)擴容為原來的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
轉(zhuǎn)成線程安全的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),實質(zhì)上存儲還是使用了構(gòu)造時傳進(jìn)來的list
,只是將list
作為底層存儲,對它做了一層包裝。正是因為多了一層封裝,所以就會比直接操作數(shù)據(jù)的Vector
慢那么一點點。
從上面的代碼我們也可以看出來,SynchronizedList
的同步,使用的是synchronized
代碼塊對mutex
對象加鎖,這個mutex
對象還能夠通過構(gòu)造函數(shù)傳進(jìn)來,也就是說我們可以指定鎖定的對象。而Vector
則使用了synchronized
方法,同步方法的作用范圍是整個方法,所以沒辦法對同步進(jìn)行細(xì)粒度的控制。而且同步方法加鎖的是this
對象,沒辦法控制鎖定的對象。這也是vector
和SynchronizedList
的一個區(qū)別。
線程安全并不"安全"
可能有些同學(xué)有在多線程環(huán)境下使用List
的需求,所以選擇了Vector
或者Collections.SynchronizedList
,然后就以為可以再多線程環(huán)境下安全地操作List
了。但是這種想法可能會導(dǎo)致代碼出現(xiàn)不可預(yù)料的錯誤,因為雖然Vector
(以Vector為例)實現(xiàn)了各個方法操作的線程安全,但是當(dāng)多個方法之間進(jì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);
// 該線程啟動之后,會結(jié)構(gòu)化修改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會不斷添加新元素,也就是會結(jié)構(gòu)化修改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
用來模擬結(jié)構(gòu)化修改Vector
的線程。在main
函數(shù)中,iteratorThread
首先開始運行,不斷迭代Vector
的值。主線程休眠5s,在這5s內(nèi),iteratorThread
是沒有問題的。5s過后,modifyVectorThread
開始運行,該線程會向Vector
內(nèi)添加元素,也就是結(jié)構(gòu)化修改Vector
。
有些同學(xué)可能覺得這段代碼不會有問題,因為Vector
是線程安全的,在多線程環(huán)境下理應(yīng)正常運行。但是這個線程安全是有缺陷的,再迭代的情況下,我們需要的實際上是對整個迭代過程加鎖,而不是對迭代器的hasNext
、next
等單獨的方法加鎖。這段代碼會報ConcurrentModificationException
異常。如圖:
解決方案很簡單,對IteratorRunnable
的迭代過程加鎖就可以了:
public void run() {
while(true) {
// 對迭代過程加鎖
synchronized (vector) {
for (Integer i : vector) {
}
}
}
}