welcome to Tong's Digital Garden
bigguaiwutong@qq.com
by tong
数据量较大时,使用了多线程去获取数据,并通过 addAll 的方式将它们添加至同一个 result (ArrayList) 集合,我们发现在这个集合中出现了 null 元素,导致下面的处理程序出现空指针问题
我们查看 ArrayList 的源码,可以知道 addAll 方法并不是原子方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
那么当多线程进行处理操作时,有可能出现以下问题
addAll 方法,这时它获取到 size 的值为 9,调用 ` ensureCapacityInternal ` 方法进行容量判断。可以容纳返回addAll 方法,它获取到 size 的值也为 9,也开始调用 ` ensureCapacityInternal ` 方法。可以容纳返回System.arraycopy 操作,它的 size 值为 9。此时复制的数组元素开始地址是 9System.arraycopy 它的 size 值也为 9。此时复制的数组元素开始地址是 9,将线程 A 复制过去的元素覆盖addAll 方法,这时它获取到 size 的值为 9,调用 ` ensureCapacityInternal ` 方法进行容量判断。可以容纳返回addAll 方法,它获取到 size 的值也为 9,也开始调用 ` ensureCapacityInternal ` 方法。可以容纳返回System.arraycopy 操作,它的 size 值为 9。此时复制的数组元素开始地址是 9System.arraycopy 它的 size 值也为 9。此时复制的数组元素开始地址是 9,将线程 A 复制过去的元素覆盖size += numNew; 后 size 为 10size += numNew; 后 size 为 11addAll 方法,这时它获取到 size 的值为 9,调用 ` ensureCapacityInternal ` 方法进行容量判断。可以容纳返回addAll 方法,它获取到 size 的值也为 9,也开始调用 ` ensureCapacityInternal ` 方法。可以容纳返回System.arraycopy 操作,而 elementData 没有进行过扩容,它的可容纳长度为 10。执行 size += numNew; 后 size 为 10System.arraycopy,而 elementData 没有进行过扩容,它的可容纳长度为 10。此时复制的数组元素开始地址是 size 值 10,将线程 A 复制后超出数组长度 10,导致 IndexOutOfBoundsException 异常避免多线程调用 addAll() 方法
List<Object> list =Collections.synchronizedList(new ArrayList<Object>);List<Object> list1 = new CopyOnWriteArrayList<Object>();注意:这两种方式都是通过加锁的方式来实现线程安全的,都将会导致性能的部分下降