ArrayList线程安全问题
众所周知,ArrayList
不是线程安全的,在并发场景使用ArrayList
可能会导致add内容为null,迭代时并发修改list内容抛ConcurrentModificationException
异常等问题。java类库里面提供了以下三个轮子可以实现线程安全的List,它们是
- Vector
- Collections.synchronizedList
- CopyOnWriteArrayList
本文简要的分析了下它们线程安全的实现机制并对它们的读,写,迭代性能进行了对比。
Vector
从JDK1.0开始,Vector
便存在JDK中,Vector
是一个线程安全的列表,底层采用数组实现。其线程安全的实现方式非常粗暴:Vector
大部分方法和ArrayList
都是相同的,只是加上了synchronized
关键字,这种方式严重影响效率,因此,不再推荐使用Vector
了。JAVA官方文档中这样描述:
If a thread-safe implementation is not needed, it is recommended to use ArrayList in place of Vector.
如果不需要线程安全性,推荐使用ArrayList替代Vector
关键源码如下:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized Iterator<E> iterator() {
return new Itr();
}
可以看到Vector
通过在方法级别上加入了synchronized
关键字实现线程安全性。
Collections.synchronizedList
因为ArrayList不是线程安全的,JDK提供了一个Collections.synchronizedList
静态方法将一个非线程安全的List(并不仅限ArrayList)包装为线程安全的List。使用方式如下:
List list = Collections.synchronizedList(new ArrayList());
根据文档,转换包装后的list可以实现add,remove,get等操作的线程安全性,但是对于迭代操作,Collections.synchronizedList
并没有提供相关机制,所以迭代时需要对包装后的list(敲黑板,必须对包装后的list进行加锁,锁其他的不行)进行手动加锁,使用方式如下:
List list = Collections.synchronizedList(new ArrayList());
//必须对list进行加锁
synchronized (list) {
Iterator i = list.iterator();
while (i.hasNext())
foo(i.next());
}
这个地方要注意两个地方:
- 迭代操作必须加锁,可以使用
synchronized
关键字修饰; - synchronized持有的监视器对象必须是
synchronized (list)
,即包装后的list,使用其他对象如synchronized (new Object())
会使add
,remove
等方法与迭代方法使用的锁不一致,无法实现完全的线程安全性。
通过源码可知Collections.synchronizedList
生成了特定同步的SynchronizedCollection
,生成的集合每个同步操作都是持有mutex
这个锁,所以再进行操作时就是线程安全的集合了。关键地方已经加了注释:
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
//ArrayList使用了SynchronizedRandomAccessList类