以前一直據說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)。