以前一直据说ArrayList线程不安全的说法,百度了一下不安全的分析和列子,感受都不太满意,最后边实在不行只好本身分析了。 java
代码实例ArrayListTestDemo1.java: 安全
package com.hjr.back16.common.util; import static java.lang.System.*; import java.util.ArrayList; import java.util.List; /** * 测试ArrayList线程不安全的例子 * @author scuechjr * @date 2016-4-24 1:29:48 */ public class ArrayListTestDemo1 { public static Counter counter = new Counter(); public static void main(String[] args) { final List<Integer> list = new ArrayList<Integer>(); // for (int i = 0; i < 10000; i++) { // list.add(i); // } for (int i = 0; i < 10; i++) { new Thread() { public void run() { for (int j = 0; j < 1000; j++) { list.add(1000*j + j); // 结果中抛出异常的代码 counter.increment(); } } }.start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } out.println("list size: " + list.size()); out.println("Counter: " + counter.getValue()); } } class Counter { private int value; public synchronized int getValue() { return value; } public synchronized int increment() { return ++value; } public synchronized int decrement() { return --value; } }Demo执行结果:
Exception in thread "Thread-5" Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49 at java.util.ArrayList.add(ArrayList.java:441) at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25) Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 51 at java.util.ArrayList.add(ArrayList.java:441) at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25) java.lang.ArrayIndexOutOfBoundsException: 54 at java.util.ArrayList.add(ArrayList.java:441) at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25) java.lang.ArrayIndexOutOfBoundsException: 50 at java.util.ArrayList.add(ArrayList.java:441) at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25) Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 52 at java.util.ArrayList.add(ArrayList.java:441) at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25) list size: 4858 Counter: 5050分析执行结果的时候,咱们可能会发现两个问题:1)为何抛出异常;2)为何list size为何比Counter小?
1)为何抛出异常 多线程
咱们先看下ArrayList.add()方法的源码: app
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }上边源码中,ensureCapacityInternal方法主要是判断须要的容量(size+1)是否大于 ArrayList的容量(elementData.length),若是大于 根据规则增长ArrayList的容量,不然 直接返回。
因为ArrayList.add()没有同步锁,因此多线程调用这个方法的时候,有可能多个线程拿到相同的size值去与ArrayList的容量作比较,而执行到elemntData[size++] = e;时倒是有序的,这时,因为ensureCapacityInternal()没有适当的扩大ArrayList的容量,从而致使插入数据的长度大于ArrayList的剩余容量,因而也就抛出了越界的异常(java.lang.ArrayIndexOutOfBoundsException),图解: 测试
2)为何list size为何比Counter小 this
对于这个问题,须要说明的是如代码所示,Counter.increment()方法是加了同步锁的,因此Counter输出的值就是list.add()成功的次数。然而,list size比Counter小,一样是由于多线程进入add方法时,拿到相同的size执行elementData[size++] = e;这句代码,而Java的自增操做又不是原子性操做,这样就致使了多个线程拿相同的size执行加1的操做,最后边就出现了list size小于Counter,也就小于世界成功执行add()方法次数的问题。 spa
代码实例ArrayListTestDemo2.java: 线程
package com.hjr.back16.common.util; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 测试ArrayList线程不安全的例子 * @author scuechjr * @date 2016-4-24 1:29:48 */ public class ArrayListTestDemo2 { public static void main(String[] args) { final List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 10000; i++) { list.add(i); } // 列表遍历线程 new Thread() { public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()) { iterator.next(); // 异常抛出的地方 } } }.start(); // 列表新增元素线程 new Thread() { public void run() { for (int j = 0; j < 1000; j++) { list.add(1000*j + j); } } }.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }Demo执行结果:
Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at com.hjr.back16.common.util.ArrayListTestDemo2$1.run(ArrayListTestDemo2.java:25)异常来源代码片断ArrayList内部类Itr:
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } ... ... final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }根据异常信息,咱们知道异常是Itr.checkForComodification()这个方法抛出的 。其中,modCount是一个ArrayList用于记录ArrayList修改(包括列表元素的增删改等)次数的成员变量;expectedModCount是初始话一个新的Iterator时的modCount值。整个方法主要用于判断ArrayList在Iterator遍历的过程当中,是否发生修改,若是发生修改(expectedModCount与modCount不一样),抛出异常(这实际上是Java集合的一种错误机制:fail-fast)。