從ArrayList到CopyOnWriteArrayList


首先,有一段代碼引出我們的問題

List<String> list=new ArrayList<>();
		list.add("靳卓1");
		list.add("靳卓2");
		list.add("大帥逼");
		System.out.println(list);
		for(String s:list){
			if("靳卓1".equals(s)){
				list.remove(s);
			}
		}
                Iterator it =list.iterator();
		while(it.hasNext()){
			String str=(String) it.next();
			if("靳卓1".equals(str)){
				it.remove();
				//list.remove("靳卓1");//此種同增強for循環
			}
		}
		        

 我們在使用增強for循環進行循環,然后刪除某個元素時會出現異常ConcurrentModificationException,我們在使用迭代器循環使用list刪除時同樣會出現這樣的問題,因為增強for循環其實使用的就是迭代器,但是我們使用迭代器刪除,就不會報錯,下面我們看一下ArrayList的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; // clear to let GC do its work

        return oldValue;
    }

刪除時首先檢查index下標是否越界,然后有一個modCount++操作,modCount是我們對list修改的次數,然后我們去查看Iterator it =list.iterator() 的iterator方法,這是ArrayList獲取迭代器的方法。

   */
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    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;

        Itr() {}

 在這里return new itr(),itr是ArrayList的一個內部類,實現了Iterator接口,我們循環時需要使用到迭代器的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];
        }

 這就是next方法邏輯,第一行有一個checkForComodification方法,點進去

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

 到這里我們就知道為什么為什么會ConcurrentModificationException異常了,我們在使用Arraylist的remove方法是進行了modcount++操作,導致modCount != expectedModCount,拋出了這個異常,但是使用迭代器的remove不會出現這個異常,查看迭代器的remove方法

  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();
            }
        }

 注意有一個expectedModCount = modCount操作,因此使用迭代器時保證了modCount和expectedModCount想等,不會出現問題,但是在多線程情況下即使使用迭代器的remove方法刪除依然會出現這個異常,其實上面我們已經說到了,arraylist迭代器的獲取每次都是新創建的一個迭代器對象,但是他們操作的集合是同一個集合,也就是多個線程對同一個arraylist集合元素進行刪除,但是他們的迭代器對象是不同的,因此一個線程調取remove方法進行了 modcount++以及expectedModCount = modCount操作,但是另一個線程並沒有expectedModCount = modCount這個過程,所以仍然會拋出異常,可以使用CopyOnWriteArrayList。

  public Iterator<E> iterator() {
        return new Itr();
    }

 查看CopyOnWriteArrayList的remove方法

  public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

 可以看到使用了ReentrantLock加鎖操作,因此自然不會出現多線程並發修改的問題,注意在這里是使用的CopyOnWriteArrayList的remove方法,不是CopyOnWriteArrayList迭代器的remove方法。此外我們可以看到在remove的時候是創建了一個新數組,在新數組上進行修改,修改完畢后講新數組設置為CopyOnWriteArrayList的原數組,我們可以進一步查看CopyOnWriteArrayList的get方法

 private E get(Object[] a, int index) {
        return (E) a[index];
    }

 get方法沒有加鎖,因此可以讀寫不互斥,修改在新數組上修改,讀取時讀取舊數組。


免責聲明!

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



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