看山聊并发:Java 中 Vector 和 SynchronizedList 的区别

简介: 本文还是折腾 Java 中的队列,上次比较了 Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList,当时感觉挺明白,后来想想又有些不理解的地方,所以今天在重新翻出来研究一下,我承认我钻了牛角尖了。

image.png

你好,我是看山。


本文还是折腾 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?


目录
相关文章
|
1天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
1天前
|
JavaScript 前端开发 Oracle
java和JavaScript的区别
java和JavaScript的区别
6 3
|
2天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程
|
7天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
9天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0
|
10天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
24 0
|
18天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
40 0
|
18天前
|
存储 缓存 安全
【企业级理解】高效并发之Java内存模型
【企业级理解】高效并发之Java内存模型
|
25天前
|
安全 Java
Java中的多线程并发控制
在Java中,多线程是实现并发执行任务的一种重要方式。然而,随着多个线程同时访问共享资源,可能会导致数据不一致和其他并发问题。因此,了解并掌握Java中的多线程并发控制机制显得尤为重要。本文将深入探讨Java的多线程并发控制,包括synchronized关键字、Lock接口、Semaphore类以及CountDownLatch类等,并通过实例代码演示其使用方法和注意事项。
12 2
|
29天前
|
Java
java的 isEmpty 和 isBlank 区别?
java的 isEmpty 和 isBlank 区别?
8 0