Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the ways to modify exceptions by bypassing iterator traversal data

2025-01-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

本篇内容主要讲解"绕过迭代器遍历时的数据修改异常的方法有哪些",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"绕过迭代器遍历时的数据修改异常的方法有哪些"吧!

前言

既然是绕过迭代器遍历时的数据修改异常,那么有必要先看一下是什么样的异常。如果在集合的迭代器遍历时尝试更新集合中的数据,比如像下面这样,我想输出 Hello,World,Java,迭代时却发现多了一个 C++ 元素,如果直接删除掉的话。

List list = new ArrayList();Collections.addAll(list, "Hello", "World", "C++", "Java");// 我想输出 Hello,World,Java,迭代时发现多一个 C++,所以直接删除掉。Iterator iterator = list.iterator();System.out.println(iterator.next());System.out.println(iterator.next());list.remove("C++");System.out.println(iterator.next());

那么我想你一定会遇到一个异常 ConcurrentModificationExceptio 。

HelloWorldjava.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)

这个异常在刚开始学习 Java 或者使用其他的非线程安全的集合过程中可能都有遇到过。导致这个报错出现的原因就和我们操作的一样,对于某些集合,不建议在遍历时进行数据修改,因为这样会数据出现不确定性。

那么如何绕过这个错误呢?这篇文章中脑洞大开的三种方式一定不会让你失望。

异常原因

这不是一篇源码分析的文章,但是为了介绍绕过这个异常出现的原因,还是要提一下的,已经知道的同学可以直接跳过。

根据上面的报错,可以追踪到报错位置 ArrayList.java 的 857 行和 907 行,追踪源码可以发现在迭代器的 next 方法的第一行,调用了 checkForComodification() 方法。

而这个方法直接进行了一个把变量 modCount 和 expectedModCount 进行了对比,如果不一致就会抛出来 ConcurrentModificationException 异常。

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

那么 modCount 这个变量存储的是什么信息呢?

/** * The number of times this list has been structurally modified. * 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. * *

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 * fail-fast behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * *

Use of this field by subclasses is optional. 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 数值记录的是列表的结构被修改的次数,结构修改是指那些改变列表大小的修改,或者以某种方式扰乱列表,从而使得正在进行的迭代可能产生不正确的结果。同时也指出了这个字段通常会在迭代器 iterator 和 listIterator 返回的结果中使用,如果 modCount 和预期的值不一样,会抛出 ConcurrentModificationException 异常。

而上面与 modCount 进行对比的字段 expectedModCount 的值,其实是在创建迭代器时,从 modCount 获取的值。如果列表结构没有被修改过,那么两者的值应该是一致的。

绕过方式一:40 多亿次循环绕过

上面分析了异常产生的位置和原因,是因为 modCount 的当前值和创建迭代器时的值有所变化。所以第一种思路很简单,我们只要能让两者的值一致就可以了。在源码 int modCount = 0; 中可以看到 modCount 的数据类型是 INT ,既然是 INT ,就是有数据范围,每次更新列表结构 modCount 都会增1,那么是不是可以增加到 INT 数据类型的值的最大值溢出到负数,再继续增加直到变回原来的值呢?如果可以这样,首先要有一种操作可以在更新列表结构的同时不修改数据。为此翻阅了源码寻找这样的方法。还真的存在这样的方法。

public void trimToSize() { modCount++; if (size

< elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); }} 上来就递增了 modCount,同时没有修改任何数据,只是把数据的存储进行了压缩。 List 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 多亿次遍历,溢出到负数,继续溢出到原值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());。可以看出来是先运行了迭代器 next 方法,然后才运行了System.out 进行输出。所以第二种思路是先把第三个元素C++ 更新为Java ,然后启动一个线程,在迭代器再次调用 next 方法后,把第四个元素移除掉。这样就输出了我们想要的结果。 List 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 ());//Output collection System.out.println(list);/** * * * Hello * World * Java * [Hello, World, Java] */

Hello,World,Java. Phaser is a new class in JDK 7 and is a phase execution processor. The value of the parties parameter at construction time is 2, which means that two participants are required to complete before proceeding to the next stage. The arriveAndAwaitAdvance method, when called, allows a party to arrive.

So the thread locks System.out, and then arriveAndAwaitAdvance causes one participant to report completion, which blocks, and after the other participant reports completion, the thread enters a loop where the main thread is not blocking.

The main thread executes System.out.println(iterator.next ());. The main thread is blocked when the iterator's value is retrieved for output due to locking within the thread. Thread processing does not continue until the last element of the collection is removed from the thread.

Bypass Method 3: Use Type Erase to put magic objects

In order to reduce the probability of errors when creating collections, we will use generics to restrict the data types we put in. In fact, generically restricted collections are also unrestricted at runtime. We can put any object. So we can use that to our advantage.

List 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 ());//Start operation ((List)list).set(2, new Object() { public String toString() { String s = list.get(3); list.remove(this); return s; }});System.out.println(iterator.next());

The code directly puts the third element into a magic object and overrides the toString() method to return the fourth element of the collection and then delete the third element, so that you can get the desired Hello,World,Java output.

At this point, I believe that everyone has a deeper understanding of "what are the methods to bypass the data modification exception when iterator traversal", so let's actually operate it! Here is the website, more related content can enter the relevant channels for inquiry, pay attention to us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report