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/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。若是以爲還有幫助的話,能夠點一下右下角的【推薦】,但願可以持續的爲你們帶來好的技術文章!想跟我一塊兒進步麼?那就【關注】我吧。html

相關文章
相關標籤/搜索