java基礎解析系列(八)---fail-fast機制及CopyOnWriteArrayList的原理


fail-fast機制及CopyOnWriteArrayList的原理

目錄

先看一個例子

class Te1 extends Thread
{
    private List<Integer> list;

    public Te1(List<Integer> list)
    {
        this.list = list;
    }

    public void run()
    {
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            int i = iterator.next();
        }
    }
}

 class Te2 extends Thread
{
    private List<Integer> list;

    public Te2(List<Integer> list)
    {
        this.list = list;
    }
    public void run()
    {
        for (int i = 0; i < list.size(); i++)
        {
            list.remove(i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list=new ArrayList();
        for (int i = 0; i <100 ; i++) {
            list.add(i);
        }
        Te1 t1=new Te1(list);
        Te2 t2=new Te2(list);
        t1.start();
        t2.start();

    }
}
  • 一個線程迭代,一個線程進行刪除,運行時拋出ConcurrentModificationException異常

ConcurrentModificationException

  • 中文意思為並發修改異常
736     public Iterator<E> iterator() {
737         return new Itr();
738     }
743     private class Itr implements Iterator<E> {
744         int cursor;       // index of next element to return
745         int lastRet = -1; // index of last element returned; -1 if no such
746         int expectedModCount = modCount;
747 
748         public boolean hasNext() {
749             return cursor != size;
750         }
751 
752         @SuppressWarnings("unchecked")
753         public E next() {
754             checkForComodification();
                ...
763         }
764 
765         public void remove() {
766             if (lastRet < 0)
767                 throw new IllegalStateException();
768             checkForComodification();
                ...
778         }
779 
780         final void checkForComodification() {
781             if (modCount != expectedModCount)
782                 throw new ConcurrentModificationException();
783         }
784     }
  • ArrayList有一個內部類Itr,從源碼可以看到這個類的next和remove方法里面都調用了一個chechForModification方法,而從這個方法(780行)的源碼可以看到,他是通過判斷modCount和expectedModCount是否相等來決定是否拋出並發修改異常
  • 同時在這個內部類可以看expectedModCount初始化為modCount(746行),后面並沒有修改
377     public boolean add(E e) {
378         ensureCapacity(size + 1);  // Increments modCount!!
            ...
381     }
178     public void ensureCapacity(int minCapacity) {
179         modCount++;
180         ...
189     }      
439     public boolean remove(Object o) {
440         if (o == null) {
441             for (int index = 0; index < size; index++)
442                 if (elementData[index] == null) {
443                     fastRemove(index);
444                     return true;
445                 }
446         } else {
447             for (int index = 0; index < size; index++)
448                 if (o.equals(elementData[index])) {
449                     fastRemove(index);
450                     return true;
451                 }
452         }
453         return false;
454     }
460     private void fastRemove(int index) {
461         modCount++;
            ...
467     }
  • 從ArrayList的add和remove方法源碼可以看到,這兩個方法都會導致modCount的改變
  • 那么可以分析為什么之前的代碼會拋出異常,線程A進行迭代,此時expectedModCount已經確定了,后面並沒有進行修改,而此時線程B同時remove,從前面知道remove會導致modCount改變,此時兩者不同導致拋出異常

fail-fast

A fail-fast system is nothing but immediately report any failure that is likely to lead to failure. When a problem occurs, a fail-fast system fails immediately.
In Java, we can find this behavior with iterators. In case, you have called iterator on a collection object, and another thread tries to modify the collection object, then concurrent modification exception will be thrown. This is called fail-fast.


  • 中文譯為快速失敗,這是一種錯誤檢測機制。
  • 對上文進行翻譯,當在對一個集合進行迭代的時候,其他線程嘗試去修改這個集合,並發修改異常會被拋出。這就叫做快速失敗。

CopyOnWriteArrayList

  • CopyOnWriteArrayList可以解決fail-fast的問題,將ArrayList替換成CopyWriteArrayList進行試驗。
public class Test {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list=new CopyOnWriteArrayList();
        for (int i = 0; i <100 ; i++) {
            list.add(i);
        }
        Te1 t1=new Te1(list);
        Te2 t2=new Te2(list);
        t1.start();
        t2.start();

    }
  • 結果發現並沒有拋出異常,下面從源碼角度來分析
  • CopyOnWriteArrayList的remove方法
469     public E remove(int index) {
470         final ReentrantLock lock = this.lock;
471         lock.lock();
472         try {
473             Object[] elements = getArray();
474             int len = elements.length;
475             E oldValue = get(elements, index);
476             int numMoved = len - index - 1;
477             if (numMoved == 0)
478                 setArray(Arrays.copyOf(elements, len - 1));
479             else {
480                 Object[] newElements = new Object[len - 1];
481                 System.arraycopy(elements, 0, newElements, 0, index);
482                 System.arraycopy(elements, index + 1, newElements, index,
483                                  numMoved);
484                 setArray(newElements);
485             }
486             return oldValue;
487         } finally {
488             lock.unlock();
489         }
490     }
99      final void setArray(Object[] a) {
100         array = a;
101     }
  • 473行獲取當前的Object數組,480行創建一個新的Object數組,再將舊的數組復制到新的數組上,484行將array指向新的數組
956     public Iterator<E> iterator() {
957         return new COWIterator<E>(getArray(), 0);
958     }
991     private static class COWIterator<E> implements ListIterator<E> {
992 
993         private final Object[] snapshot;
994 
995         private int cursor;
996 
997         private COWIterator(Object[] elements, int initialCursor) {
998             cursor = initialCursor;
999             snapshot = elements;
1000        }
1001
1002        public boolean hasNext() {
1003            return cursor < snapshot.length;
1004        }
1005
1010        @SuppressWarnings("unchecked")
1011        public E next() {
1012            if (! hasNext())
1013                throw new NoSuchElementException();
1014            return (E) snapshot[cursor++];
1015        }
1016
  • 999行將snapshot指向當前的array
  • 1011行執行next方法返回snapshot中元素,那么在遍歷的過程,如果其他線程執行remove並將array指向了新創建的數組,這個snapshot並沒有更新為新的數組,仍然指向的是remove之前的數組
  • 從CopyOnWriteArrayList的迭代器也可以發現沒有fail-fast機制.

CopyOnWriteArrayList分析

  • 修改代價大,可以從源碼知道,remove還是add方法,都會進行一次數組的復制,這樣消耗了空間(可能導致gc的頻率提高)也消耗了時間
  • 讀寫分離,讀寫不一致,讀的時候讀的是舊的數組,寫的時候寫的是新的數組,所以讀的時候不一定是最新的
  • 讀的時候不需要進行加鎖,因為寫的時候是寫在新的數組,讀的數組是舊的數組,並不會改變
  • 因此,CopyOnWriteArrayList適合讀多寫少的場景

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。


免責聲明!

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



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