你好,我是看山。
本文還是折騰 Java 中的隊列,上次比較了 Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList,當時感覺挺明白,後來想想又有些不了解的地方,是以今天在重新翻出來研究一下,我承認我鑽了牛角尖了。
Vector雖然種種問題,但是都屬于設計上的問題,為什麼不在後續版本中進行優化呢?HashMap就優化了好幾次。而SynchronizedList這個内部類(也就是通過Collections.synchronizedList(new ArrayList())建立的),也是采用了和Vector類似的同步方式(差別是一個在方法體、一個在方法塊上,差别不大),為什麼大家還是舍棄Vector呢?
其實,在 JDK 中,Vector一直沒有被标記為Deprecated,也就是說,雖然外界傳說Vector有各種問題,但是從 JDK 官方,從沒有認為這個親兒子沒用。
是以,大家不用Vector的原因就剩下兩種:
其他隊列比Vector更加适合,優中選優
大家都說Vector不好用,那我也不用了【個人感覺這種機率更大】
因為Vector主要是數組結構,是以下面大部分的對比都是比較的是針對ArrayList的同步封裝。
有了Vector為什麼還要有SynchronizedList
這個問題的答案是從 StackOverflow 中找到的。
在 JDK 1.2 之前,Collections是獨立類庫,不是 JDK/JRE 中的一部分。當時synchronized性能特别差,很多場景不需要使用同步方式,是以,獨立類庫的開發者删除了同步操作,這個應該就是ArrayList的前身。但是,少部分場景還是需要使用同步,于是就有了SynchronizedList,一個可以包裝所有List子類的包裝類,這個類在幾乎所有方法上都加上了synchronized同步,這個設計與Vector相似。
古人說“文人相輕”,其實在編碼界也是有鄙視鍊的。在這裡就是:雖然我的設計和你的設計類似,但是我的設計就是比你的好。不過,Collections确實設計更優。
一個SynchronizedList實作所有List的同步
SynchronizedList定位是包裝類,可以包裝所有List的子類。也就是說,無論是ArrayList還是LinkedList都能過實作同步,完全不會修改底層資料結構,既實作的同步,又保留了底層接口的優點。比如LinkedList的插入、删除效率,ArrayList的順序讀取。而且,一個包裝類就解決所有List子類的同步需求,完全不需要重複實作一遍。
相對而言,Vector就比較霸道了,任何想要同步的隊列,都需要轉換為Vector的數組結構。大家都知道,數組存儲需要連續空間,順序讀取效率表現優秀,但是插入和删除效率就比較差了。
将疊代器的同步權利交給使用者
同步方法中SynchronizedList和Vector很類似,不過疊代器方法有了不同想法。
看源碼就知道,SynchronizedList中的iterator和listIterator方法都沒有實作同步,是以在擷取疊代器的時候不會阻塞。
public Iterator<E> iterator() {
return list.iterator(); // Must be manually synched by user!
}
public ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
如果需要疊代的話,直接用synchronized包一下隊列對象就可以了,代碼如下:
final List<String> list = Collections.synchronizedList(new ArrayList());
list.add("A");
list.add("B");
list.add("C");
final Iterator<String> iterator = list.iterator();
synchronized (list) {
while (iterator.hasNext()) {
final String next = iterator.next();
System.out.println(next);
}
}
我們再看下Vector疊代器實作:
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
// Racy but within spec, since modifications are checked
// within or after synchronization in next/previous
return cursor != elementCount;
}
public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
// 此處省略一些方法
}
Vector的疊代器用synchronized (Vector.this)加鎖,其實也是對目前類執行個體加鎖,和我們自己實作的加鎖方式一緻。當然,從這點上來說,Vector能夠保證在開發人員無意識的情況下,避免為同步造成的錯誤,這也是Vector的一個優點。
Vector不完全一無是處
雖然Vector在其他地方敗給了Collections,但是在擴容這方面,還有一個可取之處。先看看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);
}
在計算新數組長度的時候,會檢查capacityIncrement是否大于 0,如果是,就擴容capacityIncrement的大小。就是說,在Vector中可以指定擴容大小,如果沒有指定,預設擴容到原來的 2 倍;而ArrayList隻能擴容到 1.5 倍,沒有辦法自定義擴容大小。
仔細想想,這點并沒有什麼用處。
文末總結
Vector内部結構是數組,與Collections.synchronizedList(new ArrayList())類似。
Vector可以指定擴容大小,預設是擴容到原數組長度的 2 倍;ArrayList不能指定擴容大小,直接擴容到原數組大小的 1.5 倍。
SynchronizedList是一個包裝類,可以将List子類都包裝為同步隊列,從非線程安全隊列轉為線程安全隊列,沒有性能延遲,直接包裝即可;Vector是一個基于數組的同步隊列,其他隊列想要轉換為Vector,需要有資料拷貝。
SynchronizedList的疊代器沒有做同步,需要使用者自己實作;Vector的疊代器做好了同步,開發人員不需要關心同步。
Vector至今未标記Deprecated,而且随着 JDK 釋出,也在更新實作。雖然 JDK 承諾相容,但是一直沒有标記過期,其用意不得而知。
推薦閱讀
認識 Java 中的隊列:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList
如果非要在多線程中使用 ArrayList 會發生什麼?
Why is Java Vector (and Stack) class considered obsolete or deprecated?
In java, Vector and Collections.synchronizedList are all synchronized, what’s the difference?