看山聊并发: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的原因就剩下两种:

  1. 其他队列比Vector更加适合,优中选优
  2. 大家都说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的数组结构。大家都知道,数组存储需要连续空间,顺序读取效率表现优秀,但是插入和删除效率就比较差了。

将迭代器的同步权利交给用户

同步方法中SynchronizedListVector很类似,不过迭代器方法有了不同想法。

看源码就知道,SynchronizedList中的iteratorlistIterator方法都没有实现同步,所以在获取迭代器的时候不会阻塞。

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 倍,没有办法自定义扩容大小。

仔细想想,这点并没有什么用处。

文末总结

  1. Vector内部结构是数组,与Collections.synchronizedList(new ArrayList())类似。
  2. Vector可以指定扩容大小,默认是扩容到原数组长度的 2 倍;ArrayList不能指定扩容大小,直接扩容到原数组大小的 1.5 倍。
  3. SynchronizedList是一个包装类,可以将List子类都包装为同步队列,从非线程安全队列转为线程安全队列,没有性能延迟,直接包装即可;Vector是一个基于数组的同步队列,其他队列想要转换为Vector,需要有数据拷贝。
  4. SynchronizedList的迭代器没有做同步,需要用户自己实现;Vector的迭代器做好了同步,开发人员不需要关心同步。
  5. Vector至今未标记Deprecated,而且随着 JDK 发布,也在更新实现。虽然 JDK 承诺兼容,但是一直没有标记过期,其用意不得而知。

推荐阅读

  1. Why is Java Vector (and Stack) class considered obsolete or deprecated?
  2. In java, Vector and Collections.synchronizedList are all synchronized, what’s the difference?

你好,我是看山,公众号:看山的小屋,10 年老猿,开源贡献者。游于码界,戏享人生。

公众号:看山的小屋

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 看山聊并发:Java 中 Vector 和 SynchronizedList 的区别

  • Trackback 关闭
  • 评论 (2)
    • CodeNongXW
    • 2021/08/23 3:22下午

    感谢博主分享,很好用,我也推荐一款国产接口管理工具ApiPost,特别好用,欢迎尝试下载使用。

  1. Vector不完全一无是处

    ……

    仔细想想,这点并没有什么用处。

    hahahah

return top