ConcurrentModificationExceptionから、反復器のソースコードを分析
4363 ワード
反復器ソース解析
次の(Java)コードを実行します.
以上のコード実行時に
ArrayListのソースコードに入って異常放出時の条件を確認する:
同時にexpectedModCountは、ArrayListによって実装されるIterator反復器インタフェースItrの属性であり、反復器実装クラスItrについて以下で説明する.
まとめ
リストを巡る過程で元のリストを削除する
単一スレッドの場合
マルチスレッドの場合
上記のコードは反復器のremove法を用いてlist構造を変更したが,上記のソースコード解析からremove法内部はスレッドが安全ではなく,マルチスレッドが同時にremoveする場合でも
次の(Java)コードを実行します.
ArrayList list = new ArrayList<>();
list.add(2);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
list.remove(integer);
}
以上のコード実行時に
java.util.ConcurrentModificationException
実行時異常が投げ出されます.ArrayListのソースコードに入って異常放出時の条件を確認する:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
/**
* modCount list , list size
*/
protected transient int modCount = 0;
同時にexpectedModCountは、ArrayListによって実装されるIterator反復器インタフェースItrの属性であり、反復器実装クラスItrについて以下で説明する.
private class Itr implements Iterator {
int cursor; // index of next element to return,
int lastRet = -1; // index of last element returned; -1 if no such,
// list , modCount , list.remove(), add() ,
// modCount , itr.expectedModCount , java.util.ConcurrentModificationException
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// , modCount expectedModCount , list
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;// 1
return (E) elementData[lastRet = i];// lastRet
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// , next() lastRet
ArrayList.this.remove(lastRet);
// , next() 1,
//ArrayList.this.remove(lastRet) list lastRet+1 size-1 ,
// , lastRet
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList.this.remove(lastRet)
の具体的な実装を分析します.public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0){
//remove ,
// index+1 size-1 , size
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
まとめ
リストを巡る過程で元のリストを削除する
単一スレッドの場合
ArrayList list = new ArrayList<>();
list.add(2);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
// remove list modCount = expectedModCount
iterator.remove();
}
マルチスレッドの場合
ArrayList list = new ArrayList<>();
list.add(2);
Thread thread1 = new Thread(){
public void run() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
iterator.remove();
}
};
};
Thread thread2 = new Thread(){
public void run() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
iterator.remove();
}
};
};
上記のコードは反復器のremove法を用いてlist構造を変更したが,上記のソースコード解析からremove法内部はスレッドが安全ではなく,マルチスレッドが同時にremoveする場合でも
java.util.ConcurrentModificationException
を引き起こす可能性があることが分かった.したがって、マルチスレッド同期更新ArrayListはできるだけ避けるべきであり、synchronizedをロックすることで同期更新の正常な動作を保証することもできる.