ArrayList是线程不安全的。我们来看他的add方法:
public boolean add(E e) { ensureCapacityInternal(size + 1); // 传入size+1 elementData[size++] = e; //赋值,size++ return true; }
首先调用ensureCapacityInternal方法,顾名思义:首先要确保容量,不然怎么add。我们看这个方法:
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
第一个if语句是用于初次add元素的扩容的,将会扩容到10.
(这一部分可以看https://my.oschina.net/u/3605966/blog/1338251)
看接下来的方法:
private void ensureExplicitCapacity(int minCapacity) { modCount++; //fast-fail //如果目前的容量足够的话,是不会扩容的 if (minCapacity - elementData.length > 0) //如果传入的size+1 > 目前的数组长度了,那么进行扩容操作 grow(minCapacity); }
看grow方法,就不细述了,是通过数组复制的办法进行的,扩了1.5倍
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
回到最初的add方法,接下来执行的句子:
elementData[size++] = e;
这句话可以被拆成2句:
elementData[size] = e; //1 size++; //2
这里就可以看出端倪了:假定2个线程A,B同时add一个元素,A执行到第一句,此时cpu给了B,此时size并没有加1,所以线程B所add的值就覆盖了线程A所add的,接下来size++,加了2次,elementData[size+1]就变成了null。
还有一种情况:会直接抛出异常,我们假定一个场景,此时arraylist中数组容量为4,size为3,就是还有一个空位置,此时线程A,B同时add一个元素,A执行完ensureCapacityInternal(size + 1),因为还没有到需要扩容的条件,是不会扩容的,此时容量还是4,接着B拿到CPU,也执行ensureCapacityInternal(size + 1),当然也不会执行扩容,接着继续执行,elementData[size] = e; size++; 接着cpu给了A,异常发生,此时size=4了,容量也=4,然而线程A已经执行过ensureCapacityInternal这个方法了,直接调用elementData[size] = e也就是elementData[4] = e,那么肯定就抛出异常了(数组越界)。
写一个最简单的例子:
public class ArrayListThreadSample { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); new Thread(()->{ for (int i=0; i<1000; i++) { list.add(i); } }).start(); new Thread(()->{ for (int i=0; i<1000; i++) { list.add(i); } }).start(); System.out.println(list.size()); list.forEach(o-> System.out.print(o+" ")); } }
输出结果可能直接抛异常,可能输出的size<500,当然也有可能输出正常。
接下来讲一下怎么让arraylist线程安全
未完待续。。