マルチスレッド14-コレクションを巡回するときの要素問題解析の削除
21118 ワード
1.質問
Userクラスを作成するには、次の手順に従います.
次のコードを実行します.
コレクションを巡るときに「張三」を見つけたら、張三の情報を削除したコードに問題はないが、実行結果は以下の通りである.
異常が発生したのはなぜですか?
2. 問題の分析:
この答えを得るには、コードの実行プロセスを表示する必要があります.
まず見る
の使用は避けます.iterator()は、ArrayListのiteratorメソッドを呼び出します.ソースコードは次のとおりです.
new Itr()を返します.ここで、ItrはArrayListの内部クラスコードの一つである.
では、上記のiteratorの遍歴操作はItrで実現されています.
プログラム中のitrUsers.hasNext()は、ItrのhasNext()メソッドを呼び出します.
ここでsizeはusers集合の長さを表す:size=3;cursorがintタイプのデフォルト値が0である場合、hasNextを初めて実行するときは明らかにcursor!=Sizeはtrueを返します
見てみろnext()このコードは、次のように実行されます.
セグメントコードは、まずcheckForComodification()メソッドを実行します.そのコードは次のとおりです.
ここでmodCountは親AbstractListで定義されたprotetct変数の初期値が0である. int expectedModCount = modCount; 最初は両者が一致し,次いでcursor=i+1であった.すなわちcursor=1である.
次のコードは、次のように実行できます.
ここで、次に実行するのは、
removeのソースコードは次のとおりです.
このコードで注意すべき点は2つあり、そのうちの1つはmodCount++です.すなわちmodCount=1である.もう1つはelementData[--size]=nullです.この行のコードは配列の要素を削除し、sizeを1つ減らします.
すなわちsize=2である.
上記のコードを経て、張三の情報は順調に集合から削除され、次は2回目のループを見る必要があります.
やはりhasNext()法はcursor=1 size=2のためである.ではhasNext()はtrueを返してループに成功します
では、実行を開始します.next()は、checkForComodification()メソッドを呼び出す必要がありますが、expectedModCount=0、modCount=1です.プログラムコードから例外が放出されます
プログラムコードはこれで終わります.その結果,張三を削除して異常を投げ出す.
3. 探索する
コードを削除李四に変更したら、効果はどうですか?結果は次のとおりです.
コードは正常に実行され、異常はありません.
やはり上の考え方でこの問題を分析します.
size=3最初のhasNextはtrueを返し、next()を実行してcursor=1を得、最初の成功サイクルは終了する
2回目のループを開始
size=3 2回目hasNextはtrueを返し、next()を実行してcursor=2を得、李四を削除することを発見したremoveメソッドを実行するこの時size=2、modCount=1、李四の情報を削除することに成功した
3回目のループを開始
size=2第3回hasNext cursor=2とsizeが等しいときhasNextはfalseがループに入っていないことを返し、コード実行が終了し、李四を正常に削除できる異常はない.
上記のような問題を回避するためには,集合遍歴時に集合に対してデータの削除操作を行うためにCopyOnWriteArrayListを用いる必要があり,プログラムを以下のように修正する.
CopyOnWriteArrayListの使用が成功した理由はuser.iterator()はItrではなく、CopyOnWriteArrayListのCOWIterator内部クラスを返します.
その中、COWIteratorコードは自分で研究することができる.
Userクラスを作成するには、次の手順に従います.
package cn.itcast.heima2;
public class User implements Cloneable{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof User)) {
return false;
}
User user = (User)obj;
//if(this.name==user.name && this.age==user.age)
if(this.name.equals(user.name)
&& this.age==user.age) {
return true;
}
else {
return false;
}
}
public int hashCode() {
return name.hashCode() + age;
}
public String toString() {
return "{name:'" + name + "',age:" + age + "}";
}
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {}
return object;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
}
次のコードを実行します.
package cn.itcast.heima2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class CollectionModifyExceptionTest {
public static void main(String[] args) {
Collection<User> users = new ArrayList<User>() ;
users.add(new User(" ",28));
users.add(new User(" ",25));
users.add(new User(" ",31));
Iterator<User> itrUsers = users.iterator();
while(itrUsers.hasNext()){
System.out.println("aaaa");
User user = (User)itrUsers.next();
if(" ".equals(user.getName())){
users.remove(user);
//itrUsers.remove();
} else {
System.out.println(user);
}
}
}
}
コレクションを巡るときに「張三」を見つけたら、張三の情報を削除したコードに問題はないが、実行結果は以下の通りである.
aaaa
aaaa
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at cn.itcast.heima2.CollectionModifyExceptionTest.main(CollectionModifyExceptionTest.java:17)
異常が発生したのはなぜですか?
2. 問題の分析:
この答えを得るには、コードの実行プロセスを表示する必要があります.
まず見る
Iterator<User> itrUsers = users.iterator();
の使用は避けます.iterator()は、ArrayListのiteratorメソッドを呼び出します.ソースコードは次のとおりです.
public Iterator<E> iterator() {
return new Itr();
}
new Itr()を返します.ここで、ItrはArrayListの内部クラスコードの一つである.
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];
}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
では、上記のiteratorの遍歴操作はItrで実現されています.
プログラム中のitrUsers.hasNext()は、ItrのhasNext()メソッドを呼び出します.
public boolean hasNext() {
return cursor != size;
}
ここでsizeはusers集合の長さを表す:size=3;cursorがintタイプのデフォルト値が0である場合、hasNextを初めて実行するときは明らかにcursor!=Sizeはtrueを返します
見てみろ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];
}
セグメントコードは、まずcheckForComodification()メソッドを実行します.そのコードは次のとおりです.
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
ここでmodCountは親AbstractListで定義されたprotetct変数の初期値が0である. int expectedModCount = modCount; 最初は両者が一致し,次いでcursor=i+1であった.すなわちcursor=1である.
次のコードは、次のように実行できます.
if(" ".equals(user.getName())){
ここで、次に実行するのは、
users.remove(user);
removeのソースコードは次のとおりです.
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
このコードで注意すべき点は2つあり、そのうちの1つはmodCount++です.すなわちmodCount=1である.もう1つはelementData[--size]=nullです.この行のコードは配列の要素を削除し、sizeを1つ減らします.
すなわちsize=2である.
上記のコードを経て、張三の情報は順調に集合から削除され、次は2回目のループを見る必要があります.
やはりhasNext()法はcursor=1 size=2のためである.ではhasNext()はtrueを返してループに成功します
では、実行を開始します.next()は、checkForComodification()メソッドを呼び出す必要がありますが、expectedModCount=0、modCount=1です.プログラムコードから例外が放出されます
throw new ConcurrentModificationException();
プログラムコードはこれで終わります.その結果,張三を削除して異常を投げ出す.
3. 探索する
コードを削除李四に変更したら、効果はどうですか?結果は次のとおりです.
aaaa
{name:' ',age:28}
aaaa
コードは正常に実行され、異常はありません.
やはり上の考え方でこの問題を分析します.
size=3最初のhasNextはtrueを返し、next()を実行してcursor=1を得、最初の成功サイクルは終了する
2回目のループを開始
size=3 2回目hasNextはtrueを返し、next()を実行してcursor=2を得、李四を削除することを発見したremoveメソッドを実行するこの時size=2、modCount=1、李四の情報を削除することに成功した
3回目のループを開始
size=2第3回hasNext cursor=2とsizeが等しいときhasNextはfalseがループに入っていないことを返し、コード実行が終了し、李四を正常に削除できる異常はない.
上記のような問題を回避するためには,集合遍歴時に集合に対してデータの削除操作を行うためにCopyOnWriteArrayListを用いる必要があり,プログラムを以下のように修正する.
package cn.itcast.heima2;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class CollectionModifyExceptionTest {
public static void main(String[] args) {
Collection<User> users = new CopyOnWriteArrayList<User>();
users.add(new User(" ",28));
users.add(new User(" ",25));
users.add(new User(" ",31));
Iterator<User> itrUsers = users.iterator();
while(itrUsers.hasNext()){
System.out.println("aaaa");
User user = (User)itrUsers.next();
if(" ".equals(user.getName())){
users.remove(user);
//itrUsers.remove();
} else {
System.out.println(user);
}
}
}
}
CopyOnWriteArrayListの使用が成功した理由はuser.iterator()はItrではなく、CopyOnWriteArrayListのCOWIterator内部クラスを返します.
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
その中、COWIteratorコードは自分で研究することができる.