ArrayList使用十分广泛,但它是线程不安全的,但实际使用中,我们的多线程实现,普遍都是基于一些同步方法或者锁,很多场景其实并不需要关注ArrayList本身的线程安全。

这有三种主流的实现ArrayList线程安全的方法。

一、Vector

Vector 是矢量队列,它是JDK1.0版本添加的类,历史比ArrayList(since 1.2)更为悠久。其底层和ArrayList一样是数组,除线程安全外,大多数实现方法和ArrayList逻辑基本一致。

值得一提的是,从jdk1.2以后,Java提供了系统的集合框架,就将Vector改为实现List接口,从而导致Vector里有一些重复的方法,例如:addElement(Object obj),实际上这个方法和add(Object obj)没什么区别。

Vector 的实现,就是在方法上都加上synchronized(即使get也不例外)。

/**
 * Appends the specified element to the end of this Vector.
 *
 * @param e element to be appended to this Vector
 * @return {@code true} (as specified by {@link Collection#add})
 * @since 1.2
 */
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

关于Vector为什么被弃用

  • 所有方法都有同步开销,非多线程下,效率不如ArrayList
  • 一些老代码,导致有重复的方法,以及风格和新的集合类格格不入;
  • 线程安全的实现,可以通过新的Collections.synchronizedList之类的调用来替换。

二、Collections.synchronizedList

使用collentions.synchronizedList()方法为ArrayList加锁

三、CopyOnWriteArrayList

CopyOnWriteArrayList是1.5后引入,属于JUC的一部分。他基本的原理还是和ArrayList一样,涉及线程安全的部分,是通过写时复制的方式来实现(从名字中就可以看出)。它内部有个volatile数组来保持数据。在“添加/修改/删除”数据时,会先获取互斥锁,再新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给volatile数组,然后再释放互斥锁。

因为通常需要复制整个基础数组,所以可变操作(add()set()remove() 等等)的开销很大。

四、性能比较

  • 读操作各方式基本没有区别。
  • 写时CopyOnWriteArrayList性能较差,且随着数据量的增大,几何级下跌。
  • CopyOnWriteArrayList,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。
  • Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用但是有需要同步的地方使用,比如读写操作都比较均匀的地方。