日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

基于源码去理解Iterator迭代器的Fail-Fast与Fail-Safe机制

發(fā)布時間:2023/12/29 windows 35 coder
生活随笔 收集整理的這篇文章主要介紹了 基于源码去理解Iterator迭代器的Fail-Fast与Fail-Safe机制 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原創(chuàng)/朱季謙

在Java編程當中,Iterator迭代器是一種用于遍歷如List、Set、Map等集合的工具。這類集合部分存在線程安全的問題,例如ArrayList,若在多線程環(huán)境下,迭代遍歷過程中存在其他線程對這類集合進行修改的話,就可能導致不一致或者修改異常問題,因此,針對這種情況,迭代器提供了兩種處理策略:Fail-Fast(快速失敗)和Fail-Safe(安全失敗)。

先簡單介紹下這兩種策略——

1. Fail-Fast(快速失敗)機制
快速失敗機制是指集合在迭代遍歷過程中,其他多線程或者當前線程對該集合進行增加或者刪除元素等操作,當前線程迭代器讀取集合時會立馬拋出一個ConcurrentModificationException異常,避免數(shù)據(jù)不一致。實現(xiàn)原理是迭代器在創(chuàng)建時,會獲取集合的計數(shù)變量當作一個標記,迭代過程中,若發(fā)現(xiàn)該標記大小與計數(shù)變量不一致了,就以為集合做了新增或者刪除等操作,就會拋出快速失敗的異常。在ArrayList默認啟用該機制。

2. Fail-Safe(安全失敗)機制
安全失敗機制是指集合在迭代遍歷過程中,若其他多線程或者當前線程對該集合進行修改(增加、刪除等元素)操作,當前線程迭代器仍然可以正常繼續(xù)讀取集合遍歷,而不會拋出異常。該機制的實現(xiàn),是通過迭代器在創(chuàng)建時,對集合進行了快照操作,即迭代器遍歷的是原集合的數(shù)組快照副本,若在這個過程,集合進行修改操作,會將原有的數(shù)組內(nèi)容復制到新數(shù)組上,并在新數(shù)組上進行修改,修改完成后,再將集合數(shù)組的引用指向新數(shù)組,,而讀取操作仍然是訪問舊的快照副本,故而實現(xiàn)讀寫分離,保證讀取操作的線程安全性。在CopyOnWriteArrayList默認啟用該機制。

基于這兩個策略,分別寫一個案例來說明。

一、迭代器的Fail-Fast(快速失敗)機制原理

Fail-Fast(快速失敗)機制案例,用集合ArrayList來說明,這里用一個線程就能模擬出該機制——

  public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("張三");
        list.add("李四");
        list.add("王五");
        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
            //第一次遍歷到這里,能正常打印,第二次遍歷到這里,因上一次遍歷做了list.add("李華")操作,集合已經(jīng)改變,故而出現(xiàn)Fail-Fast(快速失敗)異常
            String item = (String)iterator.next();
            list.add("李華");
            System.out.println(item);
        }
        System.out.println(list);
    }

執(zhí)行這段代碼,打印日志出現(xiàn)異常ConcurrentModificationException,說明在遍歷過程當中,操作 list.add("李華")對集合做新增操作后,就會出現(xiàn)Fail-Fast(快速失敗)機制,拋出異常,阻止繼續(xù)進行遍歷——

張三
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at ListExample.IteratorTest.main(IteratorTest.java:23)

這里面是怎么實現(xiàn)該Fail-Fast(快速失敗)機制的呢?

先來看案例里創(chuàng)建迭代器的這行代碼Iterator iterator = list.iterator(),底層是這樣的——

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

Itr類是ArrayList內(nèi)部類,實現(xiàn)了Iterator 接口,說明它本質(zhì)是ArrayList內(nèi)部一個迭代器。這里省略部分暫時無關緊要的代碼,只需關注hasNext()和next()即可——

  private class Itr implements Iterator<E> {
        int cursor;       // 迭代計數(shù)器
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;
        //判斷是否已經(jīng)迭代到最后一位
        public boolean hasNext() {
            return cursor != size;
        }
			
    		//取出當前遍歷到集合元素
			  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];
        }
    		......
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
}

再進入案例里的這行代碼 String item = (String)iterator.next()底層,也就是Itr類的public E next() {......}方法。

注意next()里的這個方法 checkForComodification(),進入到方法里,可以看到,ConcurrentModificationException異常正是在這個方法里拋出來的,它做了一個判斷,判斷modCount是否等于expectedModCount,若不等于,就拋出快速失敗異常。

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

那么,問題就簡單了,研究ArrayList快速失敗機制,本質(zhì)只需要看modCount和expectedModCount是什么,就知道ArrayList的Fail-Fast(快速失敗)機制是怎么處理的了。

在內(nèi)部類Itr中,定義int expectedModCount = modCount,說明expectedModCount是在迭代器new Itr()創(chuàng)建時,就將此時的modCount數(shù)值賦值給變量expectedModCount,意味著,在整個迭代器生命周期內(nèi),這個expectedModCount是固定的了,從變量名就可以看出,它表示集合預期修改的次數(shù),而modCount應該就是表示列表修改次數(shù)。假如迭代器創(chuàng)建時,modCount修改次數(shù)是5,那么整個迭代器生命周期內(nèi),預期的修改次數(shù)expectedModCount就只能等于5。

請注意最為關鍵的一個地方,modCount是可以變的。

先看一下在ArrayList里,這個modCount是什么?

這個modCount是定義在ArrayList的父類AbstractList里的——

/**
 *這個列表在結(jié)構(gòu)上被修改的次數(shù)。結(jié)構(gòu)修改是指改變列表,或者以其他方式擾亂它,使其迭代進步可能產(chǎn)生不正確的結(jié)果。
 *
 *該字段由迭代器和列表迭代器實現(xiàn)使用,由{@code迭代器}和{@code listtiterator}方法返回。
 *如果該字段的值發(fā)生了意外變化,迭代器(或列表)將返回該字段迭代器)將拋出{@code ConcurrentModificationException} 
 *在響應{@code next}, {@code remove}, {@code previous},{@code set}或{@code add}操作。這提供了快速故障行為。
 *
 */
protected transient int modCount = 0;

根據(jù)注釋,可以得知,這是一個專門記錄列表被修改的次數(shù),在ArrayList當中,涉及到add新增、remove刪除、fastRemove、clear等涉及列表結(jié)構(gòu)改動的操作,,都會通過modCount++形式,增加列表在結(jié)構(gòu)上被修改的次數(shù)。

modCount表示列表被修改的次數(shù)。

我們在案例代碼里,做了add操作——

while(iterator.hasNext()) {
    String item = (String)iterator.next();
    list.add("李華");
    System.out.println(item);
}

進入到ArrayList的add方法源碼里,可以看到,在add新增過程中,按照ensureCapacityInternal =》ensureExplicitCapacity執(zhí)行順序,最后通過modCount++修改了變量modCount——

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}


 private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

// overflow-conscious code
	if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}

總結(jié)一下,迭代器創(chuàng)建時,變量expectedModCount是被modCount賦值,在整個迭代器等生命周期中,變量expectedModCount值是固定的了,但在第一輪遍歷過程中,通過list.add("李華")操作,導致modCount++,最終就會出現(xiàn)expectedModCount != modCount。因此,在迭代器進行第二輪遍歷時,執(zhí)行到 String item = (String)iterator.next(),在next()里調(diào)用checkForComodification() 判斷expectedModCount是否還等于modCount,這時已經(jīng)不等于,故而就會拋出ConcurrentModificationException異常,立刻結(jié)束迭代器遍歷,避免數(shù)據(jù)不一致。

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

以上,就是集合迭代器的Fail-Fast機制原理。


二、迭代器的Fail-Safe(安全失敗)機制原理

Fail-Fast(快速失敗)機制案例,用集合CopyOnWriteArrayList來說明,這里用一個線程就能模擬出該機制——

   public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("張三");
        list.add("李四");
        list.add("王五");
        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
            String item = (String)iterator.next();
            list.add("李華");
            System.out.println(item);
        }
        System.out.println("最后全部打印集合結(jié)果:" + list);
    }

執(zhí)行這段代碼,正常打印結(jié)果,說明在迭代器遍歷過程中,對集合做了新增元素操作,并不影響迭代器遍歷,新增的元素不會出現(xiàn)在迭代器遍歷當中,但是,在迭代器遍歷完成后,再一次打印集合,可以看到新增的元素已經(jīng)在集合里了——

張三
李四
王五
最后全部打印集合結(jié)果:[張三, 李四, 王五, 李華, 李華, 李華]

Fail-Safe(安全失敗)機制在CopyOnWriteArrayList體現(xiàn),可以理解成,這是一種讀寫分離的機制。

下面就看一下CopyOnWriteArrayList是如何實現(xiàn)讀寫分離的。

先來看迭代器的創(chuàng)建Iterator iterator = list.iterator(),進入到list.iterator()底層源碼——

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

這里的COWIterator是一個迭代器,關鍵有一個地方,在創(chuàng)建迭代器對象,調(diào)用其構(gòu)造器時傳入兩個參數(shù),分別是getArray()和0。

這里的getArray()方法,獲取到一個array數(shù)組,它是CopyOnWriteArrayList集合真正存儲數(shù)據(jù)的地方。

final Object[] getArray() {
    return array;
}

另一個參數(shù)0,表示迭代器遍歷的索引值,剛開始,肯定是從數(shù)組下標0開始。

明白getArray()和0這兩個參數(shù)后,看一下迭代器創(chuàng)建new COWIterator(getArray(), 0)的情況,只需關注與本文有關的代碼即可,其他暫時省略——

static final class COWIterator<E> implements ListIterator<E> {
    //列表快照
    private final Object[] snapshot;
    //調(diào)用next返回的元素的索引
    private int cursor;

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

    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    ......

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
}

在代碼案例中,迭代器遍歷過程時,通過hasNext()判斷集合是否遍歷完成,若還有沒遍歷的元素,就會調(diào)用 String item = (String)iterator.next()取出集合對應索引的元素。

從COWIterator類的next()方法中,可以看到,其元素是根據(jù)索引cursor從數(shù)組snapshot中取出來的。

這個snapshot就相當一個快照副本,在創(chuàng)建迭代器時,即new COWIterator(getArray(), 0),通過getArray()將此時CopyOnWriteArrayList集合的array數(shù)組引用復制給COWIterator的數(shù)組snapshot,那么snapshot引用和array引用都將指向同一個數(shù)組地址了。

只需保證snapshot指向的數(shù)組地址元素不變,那么整個迭代器讀取集合數(shù)組就不會受影響。

如何做到snapshot指向的數(shù)組地址元素不變,但是又需要同時能滿足CopyOnWriteArrayList集合的新增或者刪除操作呢?

先來看一下CopyOnWriteArrayList的 list.add("李華")操作,具體實現(xiàn)能夠在這塊源碼里看到,主要以下步驟:

1、add方法用到了ReentrantLock鎖,在進行新增過程中,通過lock鎖保證線程安全。

2、Object[] elements = getArray()這里的getArray()方法,和創(chuàng)建迭代器傳的參數(shù)getArray()是同一個,都是獲取到CopyOnWriteArrayList的array數(shù)組。取出array數(shù)組以及計算其長度后,創(chuàng)建一個比array數(shù)組長度大1的新數(shù)組,通過Arrays.copyOf(elements, len + 1)將array數(shù)組元素全部復制到新數(shù)組newElements。

3、在新數(shù)組newElements進行新增元素操作。

4、將CopyOnWriteArrayList的array數(shù)組引用指向新數(shù)組newElements,這樣array=newElements,完成新增操作。

  public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
          	//獲取到CopyOnWriteArrayList的array數(shù)組
            Object[] elements = getArray();
          	//獲取array數(shù)組長度
            int len = elements.length;
            //將array數(shù)組數(shù)據(jù),全部復制到一個長度比舊數(shù)組多1的新數(shù)組里
            Object[] newElements = Arrays.copyOf(elements, len + 1);
          	//在新數(shù)組里,新增一個元素
            newElements[len] = e;
          	//將CopyOnWriteArrayList的array數(shù)組引用指向新數(shù)組newElements
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

可見,CopyOnWriteArrayList實現(xiàn)讀寫分離的原理,就是在COWIterator迭代器創(chuàng)建時,將此時的array數(shù)組指向的地址復制給snapshot,相當做了一次快照,迭代器遍歷該快照數(shù)組地址元素。

后續(xù)涉及到列表修改相關的操作,會將原始array數(shù)組全部元素復制到一個新數(shù)組上,在新數(shù)組里面進行修改操作,這樣就不會影響到迭代器遍歷原來的數(shù)組地址里的數(shù)據(jù)了。(這也表明,這種讀寫分離只適合讀多寫少,在寫多情況下,會出現(xiàn)性能問題)

新數(shù)組修改完畢后,只需將array數(shù)組引用指向新數(shù)組地址,就能完成修改操作了。

整個過程就能完成讀寫分離機制,即迭代器的Fail-Safe(安全失敗)機制。

總結(jié)

以上是生活随笔為你收集整理的基于源码去理解Iterator迭代器的Fail-Fast与Fail-Safe机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。