天天看點

看山聊并發:Java 中 Vector 和 SynchronizedList 的差別

看山聊并發:Java 中 Vector 和 SynchronizedList 的差別

你好,我是看山。

本文還是折騰 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?