当前位置: 代码网 > it编程>编程语言>Java > Java中的fail-fast机制使用详解

Java中的fail-fast机制使用详解

2025年01月09日 Java 我要评论
java的fail-fast机制使用fail-fast 机制是java集合(collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当

java的fail-fast机制使用

fail-fast 机制是java集合(collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

例如:

  • 当某一个线程 a 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程 a 访问集合时,就会抛出 concurrentmodificationexception 异常,产生 fail-fast 事件。
  • 这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。

举例代码

单线程,在foreach循环里对某些集合元素进行元素的remove/add操作的时候,会触发fail-fast机制

public static void main(string[] args){
  list<string> strlist = new arraylist<>();
  strlist.add("aa");
  strlist.add("aa");
  strlist.add("bb");
  strlist.add("cc");
  for(string str : strlist){
     if("aa".equals(str)){
       strlist.remove(str);
     }
  }
}

多线程,在一个线程读时,另一个线程写入list,读线程会fail-fast

// 测试
public class treaddemo1 {
   public static void main(string[] args){
        list<string> strlist = new arraylist<>();
        strlist.add("aa");
        strlist.add("aa");
        strlist.add("bb");
        strlist.add("cc");
        strlist.add("dd");
        new mythread1(strlist).start();
        new mythread2(strlist).start();
   }
   static class mythread1 extends thread {
       private list<string> list;
       public mythread1(list<string> list){
             this.list = list;
       }
       @override
       public void run(){
             for(string str : list){
                 try{
                     thread.sleep(100);
                 }catch(interruptedexception e){
                     e.printstacktrace();
                 }
                 system.out.println("mtthread1:"+str);
             }
       }
  }
  static class mythread2 extends thread {
       private list<string> list;
       public mythread2(list<string> list){
             this.list = list;
       }
       @override
       public void run(){
             for(int i = 0; i < list.size(); i++){
                 try{
                     thread.sleep(100);
                 }catch(interruptedexception e){
                     e.printstacktrace();
                 }
                 if("aa".equals(list.get(i))){
                     list.remove(i);
                 }
             }
             system.out.println("mtthread2:"+list);
       }
  }
}

原理

将单线程编译后的.class反编译后发现,foreach其实是依赖while循环和iterator实现的。

通过跟踪代码的异常堆栈,发现真正抛出异常的代码是:java.util.arraylist$itr.checkforcomodification();该方法实在iterator.next()方法中调用的:

final void checkforcomodification(){
      if(modcount != expectedmodcount)
           throw new concurrentmodificationexception();
}

在该方法中modcount 和 expectedmodcount进行了比较,如果二者不相等,则抛出concurrentmodificationexception 异常。

  • modcount是arraylist中的一个成员变量。表示该集合实际被修改的次数。(操作集合类的remove()、add()、clear()方法会改变这个变量值)
  • expectedmodcount是arraylist 中的一个内部类---itr(iterator接口)中的成员变量。表示这个迭代器预期该集合被修改的次数。其值随着itr被创建而初始化。只有通过迭代器对集合进行操作,该值才会改变。

所以,在使用java的集合类的时候,如果发生concurrentmodificationexception 异常,优先考虑fail-fast有关的情况。

解决方式

1)使用普通for循环进行操作

普通for循环没有使用到iterator的遍历,所以不会进行fail-fast的检验。

public static void main(string[] args){
        list<string> strlist = new arraylist<>();
        strlist.add("aa");
        strlist.add("aa");
        strlist.add("bb");
        strlist.add("cc");
        strlist.add("dd");
        for(int i = 0; i < strlist.size(); i++){
                 if("aa".equals(strlist.get(i))){
                     strlist.remove(i);
                 }
        }
   }

2)直接使用iterator 进行操作

public static void main(string[] args){
        list<string> strlist = new arraylist<>();
        strlist.add("aa");
        strlist.add("aa");
        strlist.add("bb");
        strlist.add("cc");
        strlist.add("dd");
        iterator<string> iterator = strlist.iterator();
        while(iterator.hasnext()){
             if("aa".equals(iterator.next())){
                iterator.remove();
             }
        }
   }

3)使用java 8中提供的filter 过滤

java 8 中可以把集合转换成流,对于流有一种filter操作,可以对原始stream 进行某项过滤,通过过滤的元素被留下了生成一个新的stream。

public static void main(string[] args){
        list<string> strlist = new arraylist<>();
        strlist.add("aa");
        strlist.add("aa");
        strlist.add("bb");
        strlist.add("cc");
        strlist.add("dd");
        strlist = strlist.stream().filter(e -> !"aa".equals(e)).collect(collectors.tolist());
        system.out.println(strlist);
   }

4)使用fail-safe的集合类

为了避免触发fail-fast机制导致异常,我们可以使用java中提供的一些采用了fail-safe机制的集合类。

java.util.concurrent包下的容器都是fail-safe的,可以在多线程下并发使用,并发修改。同时也可以在foreach中进行add/remove等操作。

5)也可以使用foreach循环

如果我们非常确定一个集合中,某个即将删除的元素只包含一个的话,也是可以使用foreach循环的,只要删除之后,立即结束循环体,不在继续执行遍历就可以。

public static void main(string[] args){
  list<string> strlist = new arraylist<>();
  strlist.add("aa");
  strlist.add("aa");
  strlist.add("bb");
  strlist.add("cc");
  for(string str : strlist){
     if("aa".equals(str)){
       strlist.remove(str);
       break;
     }
  }
  system.out.println(strlist);
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com