吉泽明步qvod NEWS
你的位置:美女车模 > 吉泽明步qvod > 【KONB-002】アナル中出し極上ソープランドBEST4時間 什么?for轮回也会出问题?
【KONB-002】アナル中出し極上ソープランドBEST4時間 什么?for轮回也会出问题?
发布日期:2024-08-26 06:13    点击次数:161

【KONB-002】アナル中出し極上ソープランドBEST4時間 什么?for轮回也会出问题?

阿里妹导读著述叙述了在Java编程中遭遇并处分ConcurrentModificationException极端的履历与劝诫【KONB-002】アナル中出し極上ソープランドBEST4時間。

发现问题

在一次征战过程中,业务想知谈音讯是否是退款驱动发送的,而惟一判断的圭表,是从音讯的明细(可能是乱序的)中取出惟一的一个退款明细,通过修改时刻,看它是不是临了一条更新的明细,从而判断出它是退款驱动的音讯。初来乍到的笃某不遐想索,对圭表入参内的列表使用了排序大法,通过终了compare接口,对应两个Detail的修改时刻,便很轻松的获取到了想要的值。圭表梗概如下:

public Boolean isReFundEvent(Event event){ List<Detail> details = event.getDetails();if(Collections.isEmpty(details)) {thrownew Exception(...); } Collections.sort(details, new Comparator<Detail>() { @Overridepublicint compare(Detail input1, Detail input2) {return input1.getModifiedDate().compareTo(input2.getModifiedDate()); } });return details.get(details.size() - 1).isRefund();}

上述代码看着似乎莫得任何问题,同期还经过了自测、联调以及质地验收等层层关卡,于是代码也严容庄容的插足了骨干,部署到了预发,准备通过流量回放这临了的关卡。可是,此时cdo报了一个很少遭遇的极端:

ConcurrentModificationException。

碰见极端后,排查起来很容易,梗概的代码如下,不错看到func1在遍历detail处理业务,关键会运行到func3,在履历了层层签到后,内层的isRefundEvent函数又将正在遍历的details进行了排序,导致了极端的发生。

// 外层业绩类的代码publicvoidfunc1(Event event){ List<Detail> details = event.getDetails();if(Collections.isEmpty(details)) {return;} func2(event);// 不详其他代码...// 这里对details进行遍历了for(Detail detail : details) { List<FundItem> refundItems = fundService.queryRefundItems(event.getExt().getRefundId());if(!Collections.isEmpty(refundItems)) {for (FundItem refundItem: refundItems) { func3(event, detail, refundItem); } } }}// 中间引擎层的代码publicvoidfunc2(Event event){ isReFundEvent(Event); // 这里也调用了上头那段圭表}publicvoidfund3(Event event, Detail detail, FundItem refundItem){// 这里调用到了器具类的代码,也即是上头的那段 isReFundEvent(Event);}

是以在代码施行过程中,迭代器识别到了正在迭代的列表对象在迭代过程中被修改了,抛出了ConcurrentModificationException极端,这种机制称为Fast-Fail,GPT给出的界说为:

Fast-fail机制,又称作fail-fast机制,是策动机系统中的一种狡计形而上学,它见地当系统检测到虚伪时应该尽可能快地失败和答复虚伪,而不是尝试连接施行以保抓系统运行。这一形而上学的主旨在于通过赶紧线路问题来简化系统的调试和虚伪处理,从而幸免虚伪在系统中膨胀导致更大的挫伤。在软件征战中,fast-fail机制频频愚弄在以下几个方面:1.数据结构和API狡计:举例,在Java相聚框架中,迭代器的快速失败行径是一个典型的例子。若是在迭代相聚时,相聚被结构性地修改(添加、删除元素等),迭代器会立即抛出ConcurrentModificationException,而不是尝试处理或忽略这种修改,从而可能产生不行先见的行径。2.参数检讨:圭表在施行任何操作之前检讨其参数的灵验性。若是参数不对法,则立即抛出极端(如IllegalArgumentException),不施行任何后续操作。3.断言:在代码中使用断言(assert)来考证在某个特定点上的条目是否得志。若是条目失败,则会抛出一个AssertionError,默示出现了关键不应该有的情状。4.极端处理:在检测到虚伪后立即抛出合乎的极端,而不是复返一个虚伪码并祈望调用者检讨和处理这个虚伪码。5.事务赓续:在数据库操作中,若是事务中的一个操作失败,整个事务会被坐窝回滚,而不是尝试部分提交。Fast-fail机制不一定在统共情况下皆是欲望的弃取。在某些系统中,比如需要高可用性的永诀式系统中,可能更稳健聘用fail-safe大略fail-soft政策,这些政策在检测到虚伪时会尝试一些还原大略左迁操作来保管业绩的联贯性。可是,fast-fail在征战和测试阶段越过有用,它不错匡助征战东谈主员趁早发现并建造潜在的虚伪,从而培植软件的可靠性和疗养性。

极端的旨趣很浅近,使用for轮回对列表对象进行遍历时,编译器会将for轮回优化成迭代器进行遍历,是以径直翻看迭代器的源码不错看到:

privateclassItrimplementsIterator<E> {//面前元素索引int cursor; //面前遍历到的元素索引int lastRet = -1; //存ArrayList里面得modCountint expectedModCount = modCount; Itr() {}/* * -- hasNext()圭表,判断是否还有元素。 * 因为调用next()时,cursor每次往后移动,当cursor == size时,证据遍历完了 * (因为cursor是从0运行) */public boolean hasNext(){return cursor != size; }/* * 复返面前元素 */public E next(){//此圭表即是去检讨modCount的情况。 checkForComodification();//i存储面前将要遍历的元素的索引int i = cursor;//越界检讨if (i >= size)thrownew NoSuchElementException();//获取List里面的数组 Object[] elementData = ArrayList.this.elementData;//i大于elementData.length 证据再次工夫数组仍是可能发生扩容了,抛极端if (i >= elementData.length)thrownew ConcurrentModificationException();//cursor + 1,指针后移 cursor = i + 1;//复返面前元素。return (E) elementData[lastRet = i]; } }

在迭代器被创建时,会纪录面前迭代对象被修改的次数expectedModCount,每当迭代对象(也即是List)被修改时(add、remove、sort等),对象自身的modCount属性皆会+1,最终迭代器在获取下个迭代元素前,会调用的checkForComodification圭表,通过expectedModCount与modCount进行对比,检讨迭代对象是否被修悛改。当两个值不一致时,便会抛出ConcurrentModificationException极端,何况报错堆栈的位置,亦然for轮回处。

final voidcheckForComodification(){/*检讨创建迭代器对象时的modCount与面前modCount是否疏通, *若是不同,证据面前在迭代遍历元素工夫有其他线程对List进行了add大略remove *那么径直抛出极端。 */if (modCount != expectedModCount)thrownew ConcurrentModificationException();}

既然问题仍是定位,建造决策亦然十分轻松,对底本要排序的details对象深拷贝后,得到一个副本tempDetail,然后对tempDetail进行排序,通常不错得手的赢得想要的为止。可是,在考证建造决策是否正确时,若何复现极端,形成了最大的发愤。

并不是每次皆会报错

按照上头的面孔,如斯浅近的极端,应该是一个必现问题,因为每次遍历details时,皆会进行排序,从而抛出ConcurrentModificationException。

可事实并不是这么,代码通过了统共的线下的统共测试,难谈遭遇某种“灰电均衡”?

【KONB-002】アナル中出し極上ソープランドBEST4時間

本着严肃、崇敬的责任作风,笃某又运行了新一轮的排查。

// 外层业绩类的代码publicvoidfunc1(Event event){ List<Detail> details = event.getDetails();if(Collections.isEmpty(details)) {return;} func2(event);// 不详其他代码...// 这里对details进行遍历了for(Detail detail : details) { List<FundItem> refundItems = fundService.queryRefundItems(event.getExt().getRefundId());if(!Collections.isEmpty(refundItems)) {for (FundItem refundItem: refundItems) { func3(event, detail, refundItem); } } }}// 中间引擎层的代码publicvoidfunc2(Event event){ isReFundEvent(Event); // 这里也调用了上头那段圭表}publicvoidfund3(Event event, Detail detail, FundItem refundItem){// 这里调用到了器具类的代码,也即是上头的那段 isReFundEvent(Event);}

不出丑出,代码其委果施行func2时,关键仍是调用过isReFundEvent()了,由于Collections.sort()圭表,是径直对传入的元素自己进行排序,是以比及fund3调用时,Event中的details仍是是按照修改时刻排序好的,最新的一个detail一定会出当今列表的临了一位。

性爱大师影音

此时,笃某果敢臆测:是不是迭代器在临了一次遍历时,“暗暗”修改迭代对象,迭代器是不会进行检讨的。抱着试一试的心态,笃某大开了平方作念两数之和的工程文献,写了一个demo:

publicstaticvoidmain(String[] args){ ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);int cnt = ;for(Integer ele: list) { System.out.println(ele); cnt++;// 这里cnt为1、2、3就会极端,为4时不会抛出极端if (cnt == 4) { Collections.sort(list, new Comparator<Integer>() { @Overridepublicint compare(Integer input1, Integer input2) {return input1 - input2; } }); } } }



实验效力评释了笃某的臆测,于是再一次的大开了源码,不错看到:

// 临了一次调用时,到这里就扫尾了public boolean hasNext(){return cursor != size;}/* * 复返面前元素 */public E next(){//此圭表即是去检讨modCount的情况。 checkForComodification();//i存储面前将要遍历的元素的索引int i = cursor;//越界检讨if (i >= size)thrownew NoSuchElementException();//获取List里面的数组 Object[] elementData = ArrayList.this.elementData;//i大于elementData.length 证据再次工夫数组仍是可能发生扩容了,抛极端if (i >= elementData.length)thrownew ConcurrentModificationException();//cursor + 1,指针后移 cursor = i + 1;//复返面前元素。return (E) elementData[lastRet = i];}

//迭代器遍历过程Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) { Integer next = iterator.next();// 操作next}

迭代器会在获取下一个元素的时,才会进行modCount的检讨,而当迭代器中莫得下一个元素时,会径直拆开迭代,不会走到next圭表,也就意味着不去检讨迭代器是否被修悛改。

是以,在代码func1中,固然每次遍历details时,惟有代码走到了func3,函数皆会对details进行排序,按理说极端确定会发生。但是由于,在func2处,仍是对明细进行了排序,导致若是有惟一的退款明细,极有可能出当今临了一个(因为大无数交游中,临了的操作皆是退款,很少有退款后再支付的场景)。是以施行到func3中的排序代码时,details仍是遍历到临了一位了,isReFundEvent函数不错“暗暗”的对明细进行排序,迭代器也不会再进行检讨,bug也就严容庄容的荫藏了起来。临了,笃某制造了一笔支付后先退款再支付的交游,便得手的将极端复现,修改后的代码也能无缺的处分了该问题。

复盘

ConcurrentModificationException异频频常会有单线程和多线程两种可能。

单线程:单线程报错只会是上述情况,存在嵌套在轮回内的相聚类对象自己的修改。提议在写代码的时候,使用对象副本的样子对list等相聚类自己对象进行操作,大略使用迭代器自己自带的remove和add圭表进行操作。在大型系统中,由于皆是圭表之间好多皆是高低文传递,圭表之间嵌套很深,是以出现该问题的几率照旧很大的,写代码时照旧要迟滞溯源。

多线程:多个线程同期操作兼并个相聚时,由于arrayList等类是线程不安全的,是以就会出现并发修改极端,提议在多线程操作时,使用线程安全类,大略使用器具类等对相聚进行加锁,再进行修改操作。

若是大众还有更好的处分心情,迎接大众在驳斥区交流。

另外,照旧提议大众严格顺从圭臬的研发历程,崇敬对待质地建树每一个质地卡点,因为它随时可能成为保护你我启动“接雨水”或“两数之和”的临了一关。

冷学问

在排查和盘问过程中,还学到了少量冷学问,整个共享给大众。

(也不算冷学问,在征战规约中仍是证据过了)



迭代过程中,不错通过迭代器对底本的list进行操作,不要通过list自己。举个例子:

若是径直对list调用remove圭表,会报错。





但是调用迭代器自己的remove圭表,不会报错:





原因是list的迭代器的remove圭表,会将exceptedModCount重置:



add圭表一样: