マルチスレッド14-コレクションを巡回するときの要素問題解析の削除

21118 ワード

1.質問
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コードは自分で研究することができる.