首先需要了解什么是线程安全:线程安全就是说多线程访问同一代码(对象、变量等),不会产生不确定的结果。 既然说ArrayList是线程不安全的,那么在多线程中操作一个ArrayList对象,则会出现不确定的结果。具体是怎样不确定,请看测试下面这段代码(在此测试ArrayList的add方法):
public class ArrayListInThread implements Runnable{ //线程不安全 private List threadList = new ArrayList(); //线程安全 //private List threadList = Collections.synchronizedList(new ArrayList()); @Override public void run() { try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } //把当前线程名称加入list中 threadList.add(Thread.currentThread().getName()); } public static void main(String[] args) throws InterruptedException{ ArrayListInThread listThread = new ArrayListInThread(); for(int i = 0; i < 100; i++){ Thread thread = new Thread(listThread, String.valueOf(i)); thread.start(); } //等待子线程执行完 Thread.sleep(2000); System.out.println(listThread.threadList.size()); //输出list中的值 for(int i = 0; i < listThread.threadList.size(); i++){ if(listThread.threadList.get(i) == null){ System.out.println();; } System.out.print(listThread.threadList.get(i) + " "); } } }
结果一:
结果二:
结果三:
以上执行结果说明ArrayList确实是线程不安全的,然后我们从线程并发的角度分析,为何会出现这样的结果:
首先,我们先来看一下ArrayList中的add方法是如何实现的(查看详细源码请点击):
//添加元素e public boolean add(E e) { // 确定ArrayList的容量大小 ensureCapacity(size + 1); // Increments modCount!! // 添加e到ArrayList中 elementData[size++] = e; return true; } // 确定ArrarList的容量。 // 若ArrayList的容量不足以容纳当前的全部元素,设置 新的容量=“(原始容量x3)/2 + 1” public void ensureCapacity(int minCapacity) { // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的 modCount++; int oldCapacity = elementData.length; // 若当前容量不足以容纳当前的元素个数,设置 新的容量=“(原始容量x3)/2 + 1” if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; //如果还不够,则直接将minCapacity设置为当前容量 if (newCapacity < minCapacity) newCapacity = minCapacity; elementData = Arrays.copyOf(elementData, newCapacity); } }
结果中,有的值没有出现(结果一中3没有出现),有的出现了null值,这是由于赋值时出现了覆盖。赋值语句为:
elementData
[
size
++] = e,
这条语句可拆分为两条:
1. elementData[size] = e;
2. size ++;
假设A线程执行完第一条语句时,CPU暂停执行A线程转而去执行B线程,此时ArrayList的size并没有加一,这时在ArrayList中B线程就会覆盖掉A线程赋的值,而此时,A线程和B线程先后执行size++,便会出现值为null的情况;至于结果三中出现的ArrayIndexOutOfBoundsException异常,
则是A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,
因此便出现了ArrayIndexOutOfBoundsException异常。
既然ArrayList是线程不安全的,但如果需要在多线程中使用,可以采用list<Object> list =Collections.synchronizedList(new ArrayList<Object>)来创建一个ArrayList对象。