在分析ArrayList线程安全问题以前,咱们线对此类的源码进行分析,找出可能出现线程安全问题的地方,而后代码进行验证和分析。java
ArrayList内部是使用数组保存元素的,数据定义以下:数组
transient Object[] elementData; // non-private to simplify nested class access
在ArrayList中此数组便是共享资源,当多线程对此数据进行操做的时候若是不进行同步控制,即有可能会出现线程安全问题。安全
首先咱们看一下add的源码以下:数据结构
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
此方法中有两个操做,一个是数组容量检查,另外就是将元素放入数据中。咱们先看第二个简单的开始分析,当多个线程执行顺序以下所示的时候,会出现最终数据元素个数小于指望值。 多线程
按照此顺序执行完以后,咱们能够看到,elementData[n]的只被设置了两次,第二个线程设置的值将前一个覆盖,最后size=n+1。下面使用代码进行验证此问题。并发
首先先看下如下代码,开启1000个线程,同时调用ArrayList的add方法,每一个线程向ArrayList中添加100个数字,若是程序正常执行的状况下应该是输出:ide
list size is :10000
代码以下:spa
private static List<Integer> list = new ArrayList<Integer>(); private static ExecutorService executorService = Executors.newFixedThreadPool(1000); private static class IncreaseTask extends Thread{ @Override public void run() { System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!"); for(int i =0; i < 100; i++){ list.add(i); } System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!"); } } public static void main(String[] args){ for(int i=0; i < 1000; i++){ executorService.submit(new IncreaseTask()); } executorService.shutdown(); while (!executorService.isTerminated()){ try { Thread.sleep(1000*10); }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println("All task finished!"); System.out.println("list size is :" + list.size()); }
当执行此main方法后,输出以下: 线程
从以上执行结果来看,最后输出的结果会小于咱们的指望值。即当多线程调用add方法的时候会出现元素覆盖的问题。code
在add方法源码中,咱们看到在每次添加元素以前都会有一次数组容量的检测,add中调用此方法的源码以下:
ensureCapacityInternal(size + 1);
容量检测的相关源码以下:
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
容量检测的流程图以下所示:
咱们以两个线程执行add操做来分析扩充容量可能会出现的并发问题: 当咱们新建一个ArrayList时候,此时内部数组容器的容量为默认容量10,当咱们用两个线程同时添加第10个元素的时候,若是出现如下执行顺序,可能会抛出java.lang.ArrayIndexOutOfBoundsException异常。
第二个线程往数组中添加数据的时候因为数组容量为10,而此操做往index为10的位置设置元素值,所以会抛出数组越界异常。
使用以下代码: private static List<Integer> list = new ArrayList<Integer>(3);
private static ExecutorService executorService = Executors.newFixedThreadPool(10000); private static class IncreaseTask extends Thread{ @Override public void run() { System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!"); for(int i =0; i < 1000000; i++){ list.add(i); } System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!"); } } public static void main(String[] args){ new IncreaseTask().start(); new IncreaseTask().start(); }
执行main方法后,咱们能够看到控制台输出以下:
ArrayList中其余包含对共享变量操做的方法一样会有并发安全问题,只须要按照以上的分析方法分析便可。