ConcurrentModificationException
異常,可能好多人都知道list遍歷不能直接進行刪除操做,可是你可能只是跟我同樣知道結果,可是不知道爲何不能刪除,或者說這個報錯是如何產生的,那麼咱們今天就來研究一下。
1、異常代碼
咱們先看下這段代碼,你有沒有寫過相似的代碼程序員
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
System.out.println("開始添加元素 size:" + list.size());
for (int i = 0; i < 100; i++) {
list.add(i + 1);
}
System.out.println("元素添加結束 size:" + list.size());
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next % 5 == 0) {
list.remove(next);
}
}
System.out.println("執行結束 size:" + list.size());
}
「毫無疑問,執行這段代碼以後,必然報錯,咱們看下報錯信息。」web
![](http://static.javashuo.com/static/loading.gif)
咱們能夠經過錯誤信息能夠看到,具體的錯誤是在checkForComodification
這個方法產生的。面試
2、ArrayList源碼分析
首先咱們看下ArrayList
的iterator
這個方法,經過源碼能夠發現,其實這個返回的是ArrayList
內部類的一個實例對象。服務器
public Iterator<E> iterator() {
return new Itr();
}
咱們看下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;
Itr() {}
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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
「參數說明:」app
cursor
: 下一次訪問的索引;編輯器
lastRet
:上一次訪問的索引;ide
expectedModCount
:對ArrayList修改次數的指望值,初始值爲modCount
;源碼分析
modCount
:它是AbstractList
的一個成員變量,表示ArrayList
的修改次數,經過add
和remove
方法能夠看出;測試
「幾個經常使用方法:」
hasNext()
:
public boolean hasNext() {
return cursor != size;
}
若是下一個訪問元素的下標不等於size
,那麼就表示還有元素能夠訪問,若是下一個訪問的元素下標等於size
,那麼表示後面已經沒有可供訪問的元素。由於最後一個元素的下標是size()-1
,因此當訪問下標等於size
的時候一定沒有元素可供訪問。
next()
:
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];
}
注意下,這裏面有兩個很是重要的地方,cursor
初始值是0,獲取到元素以後,cursor
加1,那麼它就是下次索要訪問的下標,最後一行,將i
賦值給了lastRet
這個其實就是上次訪問的下標。
此時,cursor
變爲了1,lastRet
變爲了0。
最後咱們看下ArrayList
的remove()
方法作了什麼?
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
「重點:」
咱們先記住這裏,modCount
初始值是0,刪除一個元素以後,modCount
自增1,接下來就是刪除元素,最後一行將引用置爲null
是爲了方便垃圾回收器進行回收。
3、問題定位
到這裏,其實一個完整的判斷、獲取、刪除已經走完了,此時咱們回憶下各個變量的值:
cursor
: 1(獲取了一次元素,默認值0自增了1);
lastRet
:0(上一個訪問元素的下標值);
expectedModCount
:0(初始默認值);
modCount
:1(進行了一次remove
操做,變成了1);
不知道你還記不記得,next()
方法中有兩次檢查,若是已經忘記的話,建議你往上翻一翻,咱們來看下這個判斷:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
當modCount
不等於expectedModCount
的時候拋出異常,那麼如今咱們能夠經過上面各變量的值發現,兩個變量的值究竟是多少,而且知道它們是怎麼演變過來的。那麼如今咱們是否是清楚了ConcurrentModificationException
異常產生的願意呢!
「就是由於,list.remove()
致使modCount
與expectedModCount
的值不一致從而引起的問題。」
4、解決問題
咱們如今知道引起這個問題,是由於兩個變量的值不一致所致使的,那麼有沒有什麼辦法能夠解決這個問題呢!答案確定是有的,經過源碼能夠發現,Iterator
裏面也提供了remove
方法。
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
你看它作了什麼,它將modCount
的值賦值給了expectedModCount
,那麼在調用next()
進行檢查判斷的時候勢必不會出現問題。
那麼之後若是須要remove
的話,千萬不要使用list.remove()
了,而是使用iterator.remove()
,這樣其實就不會出現異常了。
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
System.out.println("開始添加元素 size:" + list.size());
for (int i = 0; i < 100; i++) {
list.add(i + 1);
}
System.out.println("元素添加結束 size:" + list.size());
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next % 5 == 0) {
iterator.remove();
}
}
System.out.println("執行結束 size:" + list.size());
}
「建議:」
另外告訴你們,咱們在進行測試的時候,若是找不到某個類的實現類,由於有時候一個類有超級多的實現類,可是你不知道它到底調用的是哪一個,那麼你就經過debug
的方式進行查找,是很便捷的方法。
5、總結
其實這個問題很常見,也是很簡單,可是咱們作技術的就是把握細節,經過追溯它的具體實現,發現它的問題所在,這樣你不只僅知道這樣有問題,並且你還知道這個問題具體是如何產生的,那麼從此不論對於你平時的工做仍是面試都是莫大的幫助。
本期分享就到這裏,謝謝各位看到此處,
記得點個贊呦!
日拱一卒,功不唐捐
本文分享自微信公衆號 - 一個程序員的成長(xiaozaibuluo)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。