【Java集合】除了Vector,还有另一个提供线程安全的List是什么?

前言

金三银四,势在必得。我自信又有点紧张地走进了面试现场。

面试官:“我们先聊点Java基础吧,除了Vector,还有另一个提供线程安全的List是什么?”。

我:“我知道可以通过Collections.synchronizedList()方法,将线程不安全的List转为线程安全。"

面试官笑着说:“那么Collections.synchronizedList()的实现原理是什么?“。

我:“额…这个我不是很清楚”。

无疑,这这触及到了我的知识盲点。

面试官:“你可以了解一下SynchronizedList,没事,我们到下一个问题哈…"

面试结束后,我留下了学海无涯的泪水…

对于这个问题,我们的回答思路可以是这样:

  • SynchronizedList是什么呢?
  • 分析Vector与SynchronizedList之间的区别是什么?
  • 总结Vector和SynchronizedList之间的区别。

(若文章有不正之处,或难以理解的地方,请多多谅解,欢迎指正)

SynchronizedList是什么?

在了解SynchronizedList之前,让我们先看看SynchronizedList在什么位置:
在这里插入图片描述
我们可以看到,SynchronizedList结合了SynchronizedCollection和List的特性,这是有的小伙伴会在编辑器召唤这个类,但发现,怎么召唤不出来?因为,SynchronizedList是Collections的静态内部类

可以看到,将线程不安全的List转为线程安全的Collections.synchronizedList()方法,是通过生成SynchronizedList对象来实现的:

static <T> List<T> synchronizedList(List<T> list, Object mutex) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list, mutex) :
                new SynchronizedList<>(list, mutex));
    }

分析Vector与SynchronizedList之间的区别是什么?

上文我们知道,SynchronizedList是java.util.Collections的静态内部类,那么Vector呢?
在这里插入图片描述
可以看到,Vector跟SynchronizedList都是List的小弟,而且Vector是java.util包中的一个类。

比较二者几个重要的方法

1. add方法

Vector的实现:

public void add(int index, E element) {
    insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
    modCount++;
    if (index > elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount);
    }
    ensureCapacityHelper(elementCount + 1);
    System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
    elementData[index] = obj;
    elementCount++;
}
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

可以看到Vector的insertElementAt()方法使用了synchronized关键字来实现线程安全。不过这段代码的操作好面熟啊。是的,其实Vector与ArrayList除了线程安全方面有所不同,两者之间的代码都很相似,有兴趣的朋友可以看这篇文章:【Java集合】ArrayList的使用及原理

我们来看看ArrayList的add()方法代码,除了没有synchronized修饰方法,还真的很相似:

public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

ps:在面试的时候可以稍稍带过ArrayList的知识点,如ArrayList与Vector之间的不同有以下方面:

  • 线程安全:ArrayList是线程不安全的,Vector是线程安全的。
  • 扩容机制:ArrayList扩容为原始容量的1.5倍,而Vector扩容为原始容量的2倍。

SynchronizedList的实现:

public void add(int index, E element) {
   synchronized (mutex) {
       list.add(index, element);
   }
}

在SynchronizedList在初始化的时候,会传入一个List:

SynchronizedList(List<E> list) {
    super(list);
    this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
    super(list, mutex);
    this.list = list;
}

而这里的list.add()会根据传入的List类型,决定SynchronizedList.add()的实现方式

从上面代码可以看到,Vector使用同步方法实现线程安全,而SynchronizedList使用代码块实现

2. remove方法

SynchronizedList的实现:

public E remove(int index) {
    synchronized (mutex) {return list.remove(index);}
}

SynchronizedList中的remove()方法的实现,还是那么地随和,虽然加上了synchronized代码块,但remove()方法实现是那么地亲切,完全入乡随俗地按照传入的List类的remove()方法来进行元素的移除

Vector的实现:

public synchronized E remove(int index) {
        modCount++;
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);

        int numMoved = elementCount - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--elementCount] = null; // Let gc do its work

        return oldValue;
    }

ArrayList的实现:

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

通过以上两段代码的比对,可以看到除了Vector的remove()方法使用了synchronized关键字来实现线程安全之外,二者的实现十分相似。

总结Vector与SynchronizedList之间的不同

在总结之前,我们先回顾一下,Vector线程同步是通过同步方法来实现,SynchronizedList线程同步是通过同步代码块来实现,我们先区分一下这两种同步方式。

  1. 同步代码块在锁定的范围上可能比同步方法要小,但一般锁的范围大小和性能是成反比的。
  2. 同步块可以更加精准地控制锁的作用域,而同步方法的锁的作用域是整个方法。
  3. 同步代码块可以选择对哪个对象加锁,但是同步方法只能给this对象加锁。

目前为止,我们可以看到Vector与SynchronizedList之间的不同在于:

  1. 扩容机制:Vector的扩容机制是固定的,而SynchronizedList可以根据出传入List的不同,适配不同List的扩容规则。
  2. :SynchronizedList可以指定锁定的对象。

但其实还有两点我们忽略了:

  • SynchronizedList中的listIterator和listIterator(int index)并没有做同步处理,而Vector却对它们上了锁,也就是说在使用SynchronizedList进行遍历的时候要手动加锁。
  • 将继承了List类的子类转换成SynchronizedList,是不需要改变它的底层数据结构,可以直接使用它本身的方法。而Vector底层结构就是使用数组实现的,所以Vector不能像SynchronizedList那样适配其他List类。

所以SynchronizedList和Vector之间最主要的区别在于:

  1. SynchronizedList有很好的兼容能力,无需改变List类的子类的数据结构,就可以将它们转换成线程安全的类,而Vector不具备这种能力。
  2. SynchronizedList进行遍历时要手动进行同步处理,但Vector的遍历方法是线程安全的。
  3. SynchronizedList可以指定锁定的对象,但是Vector的锁定范围是方法。
posted @ 2020-04-04 10:46  NYfor2018  阅读(567)  评论(0编辑  收藏  举报