1 ArrayList是否线程安全?如何线程安全地操作ArrayList?
1.1 非线程安全
ArrayList是一个有序集合,底层是基于数据实现的。所以查询速度快,但是增删相对较慢。而且是一个非线程安全的集合。有多种原因会产生异常,本文只针对add()与remove()方法做阐述。 下述add()方法源码:
public boolean add(E e) {
// 1.判断列表的capacity容量是否足够,是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 真正将元素放在列表的元素数组里面
elementData[size++] = e;
return true;
}
add元素时实际上时做了两个步骤
- 判断elementData数组容量是否满足需求,是否需要扩容
- 在elementData对应的位置上设置值
下述remove()方法源码:
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;
}
由上述源码可知,假如有多个线程同时执行add()方法或者同时存在新增与删除操作,有可能会报出一个数组越界的异常ArrayIndexOutOfBoundsException。
1.2 解决方案:
1.2.1 使用Vector
Vector是一个线程安全的列表,底层采用数组实现。其线程安全的实现方式非常粗暴:Vector大部分方法和ArrayList都是相同的,只是加上synchronized关键字,这种实现方式严重影响效率。以add()方法为例:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
1.2.2 使用 Collections里面的synchronizedList
源码如下:
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
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
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator<? super E> c) {
synchronized (mutex) {list.sort(c);}
}
/**
* SynchronizedRandomAccessList instances are serialized as
* SynchronizedList instances to allow them to be deserialized
* in pre-1.4 JREs (which do not have SynchronizedRandomAccessList).
* This method inverts the transformation. As a beneficial
* side-effect, it also grafts the RandomAccess marker onto
* SynchronizedList instances that were serialized in pre-1.4 JREs.
*
* Note: Unfortunately, SynchronizedRandomAccessList instances
* serialized in 1.4.1 and deserialized in 1.4 will become
* SynchronizedList instances, as this method was missing in 1.4.
*/
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
由源码可知,synchronizedList方法除listIterator()方法外,都使用了synchronized关键词修饰。因此如果需要迭代操作必须在外面加一层锁。如下:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
1.2.3 使用CopyOnWriteArrayList
CopyOnWrite(简称:COW):即复制再写入,jdk1.5后加入的,以add()方法为例:大致流程为:加锁-->复制原始集合-->将数据添加到复制的集合-->替换原始集合为新集合-->释放锁,从而保证线程安全性。下示源码:
public boolean add(E e) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原始集合
Object[] elements = getArray();
int len = elements.length;
// 复制一个新的集合
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 替换原始集合为新集合
setArray(newElements);
return true;
}
finally {
// 释放锁
lock.unlock();
}
}
1.2.4 使用显式锁
自己在业务逻辑上使用 java.util.concurrent.locks 包中提供的显式锁(如 ReentrantLock)来手动实现对 ArrayList 的同步。以add操作为例,代码示例如下:
public void add(E e) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 在锁内部调用 ArrayList 的 add 方法
arrayList.add(e);
}finally {
lock.unlock();
}
}
1.2.5 自己实现一个MyArrayList继承自ArrayList
自己实现一个继承自ArrayList的类,按照自己的需求给相应的方法上添加synchronized关键字修饰,并调用ArrayList的方法。以add()方法为例,示例代码如下:
public class MyArrayList<E> extends ArrayList<E> {
@Override
public synchronized boolean add(E e) {
// 在 synchronized 方法中调用 ArrayList 的 add 方法
return super.add(e);
}
}