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
不适用但是有需要同步的地方使用,比如读写操作都比较均匀的地方。