Exception in thread "Thread-1" java.util.ConcurrentModificationException 異常原因和解決方法


基本上所有的集合類都會有一個叫做快速失敗的校驗機制,當一個集合在被多個線程修改並訪問時,就會出現ConcurrentModificationException 校驗機制。它的實現原理就是我們經常提到的modCount修改計數器。如果在讀列表時,modCount發生變化則會拋出ConcurrentModificationException異常。這與線程同步是兩碼事,線程同步是為了保護集合中的數據不被臟讀、臟寫而設置的。


1、單線程環境下的異常重現

    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2) {
                list.remove(integer);
                //iterator.remove();   //正確寫法
            }
        }
    }

  在while(iterator.hasNext()) 循環遍歷時,只允許刪除ArrayList 內部的  elementData[ ] 的最后一個元素,而不允許從中間刪除。
  在 iterator.next()  的源碼中,會首先執行方法:checkForComodification();  該方法:

     final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

  只有當兩個變量值相等時才不會報錯。而 list.add(2)操作和 list.remove(integer); 操作會使 modCount++;   但是變量 expectedModCount 是內部類的 Itr 子類 的 變量,該子類的實例化方法是:list.iterator();  實例對象時賦初始值:
int expectedModCount = modCount;   所以,不允許在 while(iterator.hasNext())  循環方法內 有list.add(2)操作和 list.remove(integer);操作,否則會導致expectedModCount != modCount ,進而導致  throw new ConcurrentModificationException();


2、多線程下的問題重現:
public class Vector {
    public static void main(String[] args) {
        final List<String> tickets = new ArrayList<String>();
        for(int i=0;i<100000;i++){
            tickets.add("火車票"+i);
        }
        Thread returnThread = new Thread(){
            @Override
            public void run() {
               while (true){
                   tickets.add("車票"+ new Random().nextInt());
               }
            };
        };
        Thread saleThread = new Thread(){
            @Override
            public void run() {
                for (String ticket : tickets){
                    tickets.remove(0);
                }
            };
        };
        returnThread.start();
        saleThread.start();
    }
}

  

  有可能有朋友說ArrayList是非線程安全的容器,換成Vector就沒問題了,實際上換成Vector還是會出現這種錯誤。

  原因在於,雖然Vector的方法采用了synchronized進行了同步,但是實際上通過Iterator訪問的情況下,每個線程里面返回的是不同的iterator,也即是說expectedModCount是每個線程私有。假若此時有2個線程,線程1在進行遍歷,線程2在進行修改,那么很有可能導致線程2修改后導致Vector中的modCount自增了,線程2的expectedModCount也自增了,但是線程1的expectedModCount沒有自增,此時線程1遍歷時就會出現expectedModCount不等於modCount的情況了。

  因此一般有2種解決辦法:

  1)在使用iterator迭代的時候使用synchronized或者Lock進行同步;

  2)使用並發容器CopyOnWriteArrayList代替ArrayList和Vector。

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2024 CODEPRJ.COM