最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 如何繞過迭代器遍歷時的數(shù)據(jù)修改異常

 更新時間:2021年02月19日 11:11:11   作者:達(dá)西呀  
這篇文章主要介紹了Java 繞過迭代器遍歷時的數(shù)據(jù)修改異常的方法,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下

前言

既然是繞過迭代器遍歷時的數(shù)據(jù)修改異常,那么有必要先看一下是什么樣的異常。如果在集合的迭代器遍歷時嘗試更新集合中的數(shù)據(jù),比如像下面這樣,我想輸出 Hello,World,Java,迭代時卻發(fā)現(xiàn)多了一個 C++ 元素,如果直接刪除掉的話。

List<String> list = new ArrayList<>();
Collections.addAll(list, "Hello", "World", "C++", "Java");
// 我想輸出 Hello,World,Java,迭代時發(fā)現(xiàn)多一個 C++,所以直接刪除掉。
Iterator iterator = list.iterator();
System.out.println(iterator.next());
System.out.println(iterator.next());
list.remove("C++");
System.out.println(iterator.next());

那么我想你一定會遇到一個異常 ConcurrentModificationExceptio

Hello
World

java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
	at java.util.ArrayList$Itr.next(ArrayList.java:857)
	at com.wdbyte.lab.jdk.ModCountDemo.updateCollections(ModCountDemo.java:26)

這個異常在剛開始學(xué)習(xí) Java 或者使用其他的非線程安全的集合過程中可能都有遇到過。導(dǎo)致這個報(bào)錯出現(xiàn)的原因就和我們操作的一樣,對于某些集合,不建議在遍歷時進(jìn)行數(shù)據(jù)修改,因?yàn)檫@樣會數(shù)據(jù)出現(xiàn)不確定性。

那么如何繞過這個錯誤呢?這篇文章中腦洞大開的三種方式一定不會讓你失望。

異常原因

這不是一篇源碼分析的文章,但是為了介紹繞過這個異常出現(xiàn)的原因,還是要提一下的,已經(jīng)知道的同學(xué)可以直接跳過。

根據(jù)上面的報(bào)錯,可以追蹤到報(bào)錯位置 ArrayList.java 的 857 行和 907 行,追蹤源碼可以發(fā)現(xiàn)在迭代器的 next 方法的第一行,調(diào)用了 checkForComodification() 方法。

而這個方法直接進(jìn)行了一個把變量 modCountexpectedModCount 進(jìn)行了對比,如果不一致就會拋出來 ConcurrentModificationException 異常。

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

那么 modCount 這個變量存儲的是什么信息呢?

/**
 * The number of times this list has been <i>structurally modified</i>.
 * Structural modifications are those that change the size of the
 * list, or otherwise perturb it in such a fashion that iterations in
 * progress may yield incorrect results.
 *
 * <p>This field is used by the iterator and list iterator implementation
 * returned by the {@code iterator} and {@code listIterator} methods.
 * If the value of this field changes unexpectedly, the iterator (or list
 * iterator) will throw a {@code ConcurrentModificationException} in
 * response to the {@code next}, {@code remove}, {@code previous},
 * {@code set} or {@code add} operations.  This provides
 * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
 * the face of concurrent modification during iteration.
 *
 * <p><b>Use of this field by subclasses is optional.</b> If a subclass
 * wishes to provide fail-fast iterators (and list iterators), then it
 * merely has to increment this field in its {@code add(int, E)} and
 * {@code remove(int)} methods (and any other methods that it overrides
 * that result in structural modifications to the list).  A single call to
 * {@code add(int, E)} or {@code remove(int)} must add no more than
 * one to this field, or the iterators (and list iterators) will throw
 * bogus {@code ConcurrentModificationExceptions}.  If an implementation
 * does not wish to provide fail-fast iterators, this field may be
 * ignored.
 */
protected transient int modCount = 0;

直接看源碼注釋吧,直接翻譯一下意思就是說 modCount 數(shù)值記錄的是列表的結(jié)構(gòu)被修改的次數(shù),結(jié)構(gòu)修改是指那些改變列表大小的修改,或者以某種方式擾亂列表,從而使得正在進(jìn)行的迭代可能產(chǎn)生不正確的結(jié)果。同時也指出了這個字段通常會在迭代器 iterator 和 listIterator 返回的結(jié)果中使用,如果 modCount 和預(yù)期的值不一樣,會拋出 ConcurrentModificationException 異常。

而上面與 modCount 進(jìn)行對比的字段 expectedModCount 的值,其實(shí)是在創(chuàng)建迭代器時,從 modCount 獲取的值。如果列表結(jié)構(gòu)沒有被修改過,那么兩者的值應(yīng)該是一致的。

繞過方式一:40 多億次循環(huán)繞過

上面分析了異常產(chǎn)生的位置和原因,是因?yàn)?modCount 的當(dāng)前值和創(chuàng)建迭代器時的值有所變化。所以第一種思路很簡單,我們只要能讓兩者的值一致就可以了。在源碼 int modCount = 0; 中可以看到 modCount 的數(shù)據(jù)類型是 INT ,既然是 INT ,就是有數(shù)據(jù)范圍,每次更新列表結(jié)構(gòu) modCount 都會增1,那么是不是可以增加到 INT 數(shù)據(jù)類型的值的最大值溢出到負(fù)數(shù),再繼續(xù)增加直到變回原來的值呢?如果可以這樣,首先要有一種操作可以在更新列表結(jié)構(gòu)的同時不修改數(shù)據(jù)。為此翻閱了源碼尋找這樣的方法。還真的存在這樣的方法。

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

上來就遞增了 modCount,同時沒有修改任何數(shù)據(jù),只是把數(shù)據(jù)的存儲進(jìn)行了壓縮。

List<String> list = new ArrayList<>();
Collections.addAll(list, "Hello", "World", "C++", "Java");

list.listIterator();
Iterator iterator = list.iterator();
System.out.println(iterator.next());
System.out.println(iterator.next());
list.remove("C++");
// 40 多億次遍歷,溢出到負(fù)數(shù),繼續(xù)溢出到原值
for (int n = Integer.MIN_VALUE; n < Integer.MAX_VALUE; n++) ((ArrayList) list).trimToSize();
System.out.println(iterator.next());

正確輸出了想要的 Hello,World,Java 。

繞過方式二:線程加對象鎖繞過

分析一下我們的代碼,每次輸出的都是 System.out.println(iterator.next());??梢钥闯鰜硎窍冗\(yùn)行了迭代器 next 方法,然后才運(yùn)行了System.out 進(jìn)行輸出。所以第二種思路是先把第三個元素C++ 更新為Java ,然后啟動一個線程,在迭代器再次調(diào)用 next 方法后,把第四個元素移除掉。這樣就輸出了我們想要的結(jié)果。

List<String> list = new ArrayList<>();
Collections.addAll(list, "Hello", "World", "C++", "Java");

list.listIterator();
Iterator iterator = list.iterator();
System.out.println(iterator.next());
System.out.println(iterator.next());

// 開始操作
list.set(2, "Java");
Phaser phaser = new Phaser(2);
Thread main = Thread.currentThread();
new Thread(() -> {
    synchronized (System.out) {
        phaser.arriveAndDeregister();
        while (main.getState() != State.BLOCKED) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.remove(3);
    }
}).start();
phaser.arriveAndAwaitAdvance();

System.out.println(iterator.next());

// 輸出集合
System.out.println(list);

/**
 * 得到輸出
 * 
 * Hello
 * World
 * Java
 * [Hello, World, Java]
 */

正確輸出了想要的 Hello,World,Java 。這里簡單說一下代碼中的思路,Phaser 是 JDK 7 的新增類,是一個階段執(zhí)行處理器。構(gòu)造時的參數(shù) parties 的值為2,說明需要兩個參與方完成時才會進(jìn)行到下一個階段。而 arriveAndAwaitAdvance 方法被調(diào)用時,可以讓一個參與方到達(dá)。

所以線程中對 System.out 進(jìn)行加鎖,然后執(zhí)行 arriveAndAwaitAdvance 使一個參與方報(bào)告完成,此時會阻塞,等到另一個參與方報(bào)告完成后,線程進(jìn)入到一個主線程不為阻塞狀態(tài)時的循環(huán)。

這時主線程執(zhí)行 System.out.println(iterator.next()); 。獲取到迭代器的值進(jìn)行輸出時,因?yàn)榫€程內(nèi)的加鎖原因,主線程會被阻塞。知道線程內(nèi)把集合的最后一個元素移除,線程處理完成才會繼續(xù)。

繞過方式三:利用類型擦除放入魔法對象

在創(chuàng)建集合的時候?yàn)榱藴p少錯誤概率,我們會使用泛型限制放入的數(shù)據(jù)類型,其實(shí)呢,泛型限制的集合在運(yùn)行時也是沒有限制的,我們可以放入任何對象。所以我們可以利用這一點(diǎn)做些文章。

List<String> list = new ArrayList<>();
Collections.addAll(list, "Hello", "World", "C++", "Java");

list.listIterator();
Iterator iterator = list.iterator();
System.out.println(iterator.next());
System.out.println(iterator.next());

// 開始操作
((List)list).set(2, new Object() {
    public String toString() {
        String s = list.get(3);
        list.remove(this);
        return s;
    }
});

System.out.println(iterator.next());

代碼里直接把第三個元素放入了一個魔法對象,重寫了 toString() 方法,內(nèi)容是返回集合的第四個元素,然后刪除第三個元素,這樣就可以得到想要的 Hello,World,Java 輸出。

上面就是繞過迭代器遍歷時的數(shù)據(jù)修改報(bào)錯的三種方法了,不管實(shí)用性如何,我覺得每一種都是大開腦洞的操作,這些操作都需要對某個知識點(diǎn)有一定的了解

以上就是Java 如何繞過迭代器遍歷時的數(shù)據(jù)修改異常的詳細(xì)內(nèi)容,更多關(guān)于Java 遍歷時的數(shù)據(jù)修改異常的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringIoC與SpringDI詳解

    SpringIoC與SpringDI詳解

    本文介紹了Spring框架中的IoC(控制反轉(zhuǎn))和DI(依賴注入)概念,以及如何在Spring中使用這些概念來管理對象和依賴關(guān)系,感興趣的朋友一起看看吧
    2025-03-03
  • Java代理模式與動態(tài)代理之間的關(guān)系以及概念

    Java代理模式與動態(tài)代理之間的關(guān)系以及概念

    代理模式是開發(fā)中常見的一種設(shè)計(jì)模式,使用代理模式可以很好的對程序進(jìn)行橫向擴(kuò)展。動態(tài)代理:代理類在程序運(yùn)行時被創(chuàng)建的代理方式。關(guān)鍵在于動態(tài),程序具有了動態(tài)特性,可以在運(yùn)行期間根據(jù)不同的目標(biāo)對象生成動態(tài)代理對象
    2023-02-02
  • 在Java中為日期增加一天的多種方法

    在Java中為日期增加一天的多種方法

    這篇文章主要給大家介紹了關(guān)于如何在Java中為日期增加一天的多種方法,在JAVA業(yè)務(wù)代碼中,經(jīng)常會遇到通過指定時間,增加指定天數(shù)的業(yè)務(wù)需求,需要的朋友可以參考下
    2023-07-07
  • Java中生成唯一ID的方法示例

    Java中生成唯一ID的方法示例

    這篇文章主要介紹了Java中生成唯一ID的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • idea中提示Class 'xxx' is never used的解決

    idea中提示Class 'xxx' is never us

    這篇文章主要介紹了idea中提示Class 'xxx' is never used的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常

    Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常

    這篇文章主要介紹了Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常,@ControllerAdvice ,@ControllerAdvice是一個非常有用的注解,顧名思義,這是一個增強(qiáng)的 Controller,一般配合@ExceptionHandler使用來處理全局異常,需要的朋友可以參考下
    2024-01-01
  • Java數(shù)字轉(zhuǎn)中文大寫工具類詳細(xì)代碼(拿去即用)

    Java數(shù)字轉(zhuǎn)中文大寫工具類詳細(xì)代碼(拿去即用)

    最近項(xiàng)目中用到金額轉(zhuǎn)大寫的地方,索性給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于Java數(shù)字轉(zhuǎn)中文大寫工具類的相關(guān)資料,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下
    2024-05-05
  • Java攔截器Interceptor實(shí)現(xiàn)原理及代碼示例

    Java攔截器Interceptor實(shí)現(xiàn)原理及代碼示例

    本文詳細(xì)講解了Java攔截器Interceptor實(shí)現(xiàn)原理及代碼示例,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-12-12
  • Java實(shí)現(xiàn)統(tǒng)計(jì)字符串出現(xiàn)的次數(shù)

    Java實(shí)現(xiàn)統(tǒng)計(jì)字符串出現(xiàn)的次數(shù)

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)統(tǒng)計(jì)字符串出現(xiàn)的次數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • Java實(shí)現(xiàn)四連環(huán)棋游戲

    Java實(shí)現(xiàn)四連環(huán)棋游戲

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)四連環(huán)棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-10-10

最新評論

聂荣县| 通榆县| 鸡西市| 织金县| 栖霞市| 安图县| 海口市| 洪洞县| 华容县| 昔阳县| 威海市| 新闻| 乐安县| 平山县| 广德县| 乌恰县| 浮梁县| 公安县| 祥云县| 甘德县| 库伦旗| 镇江市| 崇阳县| 乐山市| 甘德县| 泾源县| 彰化市| 富阳市| 沁阳市| 五莲县| 黄山市| 郎溪县| 磐石市| 洪洞县| 哈巴河县| 德阳市| 陕西省| 米脂县| 淅川县| 和龙市| 江华|