程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 【JAVA集合】集合迭代器快速失敗行為及CopyOnWriteArrayList,copyonwritearraylist

【JAVA集合】集合迭代器快速失敗行為及CopyOnWriteArrayList,copyonwritearraylist

編輯:JAVA綜合教程

【JAVA集合】集合迭代器快速失敗行為及CopyOnWriteArrayList,copyonwritearraylist


以下內容基於jdk1.7.0_79源碼;

什麼是集合迭代器快速失敗行為

以ArrayList為例,在多線程並發情況下,如果有一個線程在修改ArrayList集合的結構(插入、移除...),而另一個線程正在用迭代器遍歷讀取集合中的元素,此時將拋出ConcurrentModificationException異常導致迭代遍歷失敗;

ArrayList.Itr迭代器快速失敗源碼及例子

查看ArrayList的Itr迭代器源碼,可以看到Itr為ArrayList的私有內部類,有一個expectedModCount成員屬性,在迭代器對象創建的時候初始化為ArrayList的modCount,即當迭代器對象創建的時候,會將集合修改次數modCount存到expectedModCount裡,然後每次遍歷取值的時候,都會拿ArrayList集合修改次數modCount與迭代器的expectedModCount比較,如果發生改變,說明集合結構在創建該迭代器後已經發生了改變,直接拋出ConcurrentModificationException異常,如下代碼;

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

先舉一個不是多線程的簡單例子,在創建迭代器後,往ArrayList插入一條數據,然後利用迭代器遍歷,如下代碼,將拋出ConcurrentModificationException異常:

package com.pichen.basis.col;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 0; i < 10; i++){
            list.add(i);
        }
        
        Iterator<Integer> iterator = list.iterator();
        list.add(10);
        while(iterator.hasNext()){
            iterator.next();
        }
    }
}
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at com.pichen.basis.col.Main.main(Main.java:18)

再來個多線程的例子,我們創建一個t1線程,循環往集合插入數據,另外主線程獲取集合迭代器遍歷集合,如下代碼,在遍歷的過程中將拋出ConcurrentModificationException異常:

package com.pichen.basis.col;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

class ThreadTest implements Runnable{
    private List<Integer> list;
    public ThreadTest(List<Integer> list) {
        this.list = list;
    }
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.list.add(10);
        }
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        ThreadTest t1 = new ThreadTest(list);
        new Thread(t1).start();

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next() + " ");
            Thread.sleep(80);
        }
    }
}

結果打印:

0 1 2 3 4 5 6 Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at com.pichen.basis.col.Main.main(Main.java:44)

ConcurrentModificationException異常解決辦法

對ArrayList集合修改次數modCount加鎖或使用CopyOnWriteArrayList替換ArrayList,建議使用CopyOnWriteArrayList

什麼是CopyOnWriteArrayList

看名字就知道,在往集合寫操作的時候,復制集合;更具體地說,是在對集合結構進行修改的操作時,復制一個新的集合,然後在新的集合裡進行結構修改(插入、刪除),修改完成之後,改變原先集合內部數組的引用為新集合即可;

CopyOnWriteArrayList補充說明

CopyOnWriteArrayList類實現List<E>, RandomAccess, Cloneable, java.io.Serializable接口

與ArrayList功能類似,同樣是基於動態數組實現的集合;

使用CopyOnWriteArrayList迭代器遍歷的時候,讀取的數據並不是實時的;

每次對集合結構進行修改時,都需要拷貝數據,占用內存較大;

源碼查看

先看個簡單的例子,add方法,如下,調用了Arrays.copyOf方法,拷貝舊數組到新數組,然後修改新數組的值,並修改集合內部數組的引用:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

再查看CopyOnWriteArrayList迭代器類的實現,部分代碼如下, 在創建COWIterator迭代器的時候,仔細查看其構造器源碼,需要將集合內部數組的引用傳給迭代器對象,由於在集合修改的時候,操作都是針對新的拷貝數組,所以迭代器內部舊數組對象不會改變,保證迭代期間數據不會混亂(雖然不是實時的數據):

    private static class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
        ........

結果驗證

利用前面拋出ConcurrentModificationException的例子,驗證使用CopyOnWriteArrayList,

首先,創建一個t1線程,循環往集合插入數據,另外主線程獲取集合迭代器遍歷集合,代碼如下,成功運行,並打印出了舊集合的數據(注意數據並不是實時的)。

package com.pichen.basis.col;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class ThreadTest implements Runnable{

    private List<Integer> list;
    public ThreadTest(List<Integer> list) {
        this.list = list;
    }
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.list.add(10);
        }
    }
}

public class Main {

    public static void main(String[] args) throws InterruptedException {

        List<Integer> list = new CopyOnWriteArrayList<Integer>();
        for(int i = 0; i < 10; i++){
            list.add(i);
        }

        ThreadTest t1 = new ThreadTest(list);
        new Thread(t1).start();

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next() + " ");
            Thread.sleep(80);
        }
    }
}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved