多條線程併發時,若涉及到對ArrayList的add或remove,即便這些集合看起來並不存在可見性問題,每條線程處理各自的集合,但任然會觸發ConcurrentModificationException。java
所以回過頭來從新認識一下這個最經常使用的集合,本文主要擼一遍源碼及註釋,並探究一下拋出異常的緣由。數組
Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)併發
ArrayList是一個實現List接口的可變數組,它實現了列表的全部操做,並容許包括null在內的各類元素。此外,ArrayList還提供了一些方法,能夠修改內部,用於存儲列表的數組的大小。它大體與Vector相同,只不過不能同步。app
The size, isEmpty, get, set, iterator, and listIterator operations run in constant time. The add operation runs in amortized constant time, that is, adding n elements requires O(n) time. All of the other operations run in linear time (roughly speaking). The constant factor is low compared to that for the LinkedList implementation.ide
ArrayList中,其中size,isEmpty, get, set, iterator, listIterator操做以固定時間constant time執行,add操做是以分攤時間成本的方式執行的,這意味着添加n個元素,就須要O(n)的時間複雜度,而粗略地來講,其餘全部操做的時間複雜度都是線性的。 相比LinkedList而言,ArrayList的時間常量constant較低。ui
Each ArrayList instance has a capacity. The capacity is the size of the array used to store the elements in the list. It is always at least as large as the list size. As elements are added to an ArrayList, its capacity grows automatically. The details of the growth policy are not specified beyond the fact that adding an element has constant amortized time cost.this
每一個ArrayList實例的容量是列表內部用於存儲元素的數組的大小,集合的容量大於等於列表的長度,向集合內添加元素的時候,容量也會自動增加,但增加策略並不會被指定,由於實際上,這並不止分攤固定時間消耗那麼簡單。線程
An application can increase the capacity of an ArrayList instance before adding a large number of elements using the ensureCapacity operation. This may reduce the amount of incremental reallocation.debug
在向集合添加大量元素以前,能夠調用ensureCapacity(int minCapacity)來指增長集合容量,這能夠減輕不斷增大容量的重分配數量。 扒開源碼,能夠發現這樣的一句註釋設計
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access
ArrayList使用一個緩衝數組elementData來實現可變數組的,固然,數組類型天然是Object的了,修飾符爲transiant,簡而言之,就是不會被序列化。
數組elementData的長度纔是集合ArrayList的容量。 第一次add元素的時候,這個數組會被「擴展」到默認容量DEFAULT_CAPACITY,細節以下。
List<String> list = new ArrayList<>(); list.add("a"); /*****************************************************************/ /** * Appends the specified element to the end of this list. */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { //表明初始空的ArrayList 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); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. */ 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); }
可見添加元素分爲兩步
有意思的是,擴容這一步操做發生在添加第1個元素,添加第11個元素,添加第16個元素,...
int newCapacity = oldCapacity + (oldCapacity >> 1)
能夠多嘗試使用這樣的位運算,注意要加括號。
modCount繼承自AbstractList,當對集合增刪的時候會作出修改,主要被Iterator用來迭代。
Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list: List list = Collections.synchronizedList(new ArrayList(...));
ArrayList並非同步的。 若是多條線程併發訪問,只要有一條線程試圖改變ArrayList的列表結構,就必須在外部同步,改變列表機構的意思是指add、delete、clear等明確地改變內部數組大小的操做,只改變某個元素的值並不算在結構上的修改。 若是要修改,一般是同步封裝ArrayList的對象來實現的,若是沒被封裝,那就須要在建立的時候,用Collection.synchronizedList()包裝。
List<String> list = Collections.synchronizedList(new ArrayList<>());
The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Collection接口最終繼承了Iterable接口,所以ArrayList的iterator()方法將返回一個Iterator接口的實例,細細想一想其中多態的感受,Xxxable的接口規定了能力,Xxxor的接口定義了父態,這樣的設計將迭代分了開來。
除了iterator(),還有listIterator()能夠得到該list的迭代器,只不過,所得到的iterator是fail-fast的,在這種機制下,迭代過程當中只要有除iterator自己的remove()和add(),其餘任何改變list結構的操做都會使iterator報異常。 ConcurrentModificationException。 所以,面對併發修改,iterator果斷報錯(fast fail),而不會讓不肯定的事情在不肯定的時間發生。但實際上,單線程時,iterator一樣會報錯。
Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
須要注意的是,iterator的fail-fast並未如它保證的那樣機智,通常來講,在未同步的併發修改中,是不可能嚴格保證fail-fast的,也就是說,有可能沒跑出異常,iterators盡力而爲,best-effort。所以,不能依賴這個異常糾錯,只能用來debug。