pano2vr-bonjour服务已被禁用

updatefail
2023年4月1日发(作者:windows server2003)

fail-fast快速失败机制

fail-fast俗称快速失败,是在多线程进⾏迭代操作时产⽣冲突的⼀种异常抛出机制,下⾯我们就由ArrayList来深⼊理解Java中

的fail-fast机制.

-fast简介

“快速失败”也就是fail-fast,它是Java集合的⼀种错误检测机制。某个线程在对collection进⾏迭代时,不允许其他线程对该collection

进⾏结构上的修改。

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上

⾯的修改,⽽不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException异常,从⽽产⽣fail-

fast。

迭代器的快速失败⾏为⽆法得到保证,它不能保证⼀定会出现该错误,因此,ConcurrentModificationException应该仅⽤于检测bug。

包中的所有集合类都是快速失败的,⽽rent包中的集合类都是安全失败的;

快速失败的迭代器抛出ConcurrentModificationException,⽽安全失败的迭代器从不抛出这个异常。

fail-fast的出现场景

在我们常见的java集合中就可能出现fail-fast机制,⽐如ArrayList,HashMap。在多线程和单线程环境下都有可能出现快速失败。

1、单线程环境下的fail-fast:

ArrayList发⽣fail-fast例⼦:

publicstaticvoidmain(String[]args){

Listlist=newArrayList<>();

for(inti=0;i<10;i++){

(i+"");

}

Iteratoriterator=or();

inti=0;

while(t()){

if(i==3){

(3);

}

n(());

i++;

}

}

该段代码定义了⼀个Arraylist集合,并使⽤迭代器遍历,在遍历过程中,刻意在某⼀步迭代中remove⼀个元素,这个时候,就会发⽣fail-

fast。

fail-fast的原理

fail-fast是如何抛出ConcurrentModificationException异常的,⼜是在什么情况下才会抛出?

我们知道,对于集合如list,map类,我们都可以通过迭代器来遍历,⽽Iterator其实只是⼀个接⼝,具体的实现还是要看具体的集合类中的

内部类去实现Iterator并实现相关⽅法。这⾥我们就以ArrayList类为例。在ArrayList中,当调⽤or()时,其源码是:

publicIteratoriterator(){

returnnewItr();

}

即它会返回⼀个新的Itr类,⽽Itr类是ArrayList的内部类,实现了Iterator接⼝,下⾯是该类的源码:

/**

*

*/

privateclassItrimplementsIterator{

intcursor;//indexofnextelementtoreturn

intlastRet=-1;//indexoflastelementreturned;-1ifnosuch

intexpectedModCount=modCount;

publicbooleanhasNext(){

returncursor!=size;

}

@SuppressWarnings("unchecked")

publicEnext(){

checkForComodification();

inti=cursor;

if(i>=size)

thrownewNoSuchElementException();

Object[]elementData=tData;

if(i>=)

thrownewConcurrentModificationException();

cursor=i+1;

return(E)elementData[lastRet=i];

}

publicvoidremove(){

if(lastRet<0)

thrownewIllegalStateException();

checkForComodification();

try{

(lastRet);

cursor=lastRet;

lastRet=-1;

expectedModCount=modCount;

}catch(IndexOutOfBoundsExceptionex){

thrownewConcurrentModificationException();

}

}

@Override

@SuppressWarnings("unchecked")

publicvoidforEachRemaining(Consumer<?superE>consumer){

eNonNull(consumer);

finalintsize=;

inti=cursor;

if(i>=size){

return;

}

finalObject[]elementData=tData;

if(i>=){

thrownewConcurrentModificationException();

}

while(i!=size&&modCount==expectedModCount){

((E)elementData[i++]);

}

//updateonceatendofiterationtoreduceheapwritetraffic

cursor=i;

lastRet=i-1;

checkForComodification();

}

finalvoidcheckForComodification(){

if(modCount!=expectedModCount)

thrownewConcurrentModificationException();

}

}

避免fail-fast

了解了fail-fast机制的产⽣原理,接下来就看看如何解决fail-fast

⽅法1

在单线程的遍历过程中,如果要进⾏remove操作,可以调⽤迭代器的remove⽅法⽽不是集合类的remove⽅法。看看ArrayList中迭代器

的remove⽅法的源码:

publicvoidremove(){

if(lastRet<0)

thrownewIllegalStateException();

checkForComodification();

try{

(lastRet);

cursor=lastRet;

lastRet=-1;

expectedModCount=modCount;

}catch(IndexOutOfBoundsExceptionex){

thrownewConcurrentModificationException();

}

}

可以看到,该remove⽅法并不会修改modCount的值,并且不会对后⾯的遍历造成影响,因为该⽅法remove不能指定元素,只能remove

当前遍历过的那个元素,所以调⽤该⽅法并不会发⽣fail-fast现象。该⽅法有局限性。

例⼦:

publicstaticvoidmain(String[]args){

Listlist=newArrayList<>();

for(inti=0;i<10;i++){

(i+"");

}

Iteratoriterator=or();

inti=0;

while(t()){

if(i==3){

();//迭代器的remove()⽅法

}

n(());

i++;

}

}

⽅法2

使⽤java并发包(rent)中的类来代替ArrayList和hashMap。

⽐如使⽤CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使⽤上跟ArrayList⼏乎⼀样,CopyOnWriter是写时

复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进⾏修改,⽽是将原数组拷贝⼀份,

在新数组上进⾏修改,待完成后,才将指向旧数组的引⽤指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发⽣fail-fast

现象。但CopyOnWrite容器只能保证数据的最终⼀致性,不能保证数据的实时⼀致性。

对于HashMap,可以使⽤ConcurrentHashMap,ConcurrentHashMap采⽤了锁机制,是线程安全的。在迭代⽅

⾯,ConcurrentHashMap使⽤了⼀种不同的迭代⽅式。在这种迭代⽅式中,当iterator被创建后集合再发⽣改变就不再是抛出

ConcurrentModificationException,取⽽代之的是在改变时new新的数据从⽽不影响原有的数据,iterator完成后再将头指针替换为新

的数据,这样iterator线程可以使⽤原来⽼的数据,⽽写线程也可以并发的完成改变。即迭代不会发⽣fail-fast,但不保证获取的是最新的

数据。

更多推荐

updatefail