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

 更新时间:2025年01月08日 14:53:10   作者:夜夜流光相皎洁_小宁  
fail-fast机制是Java集合中用于检测并发修改的一种机制,当一个线程遍历集合时,如果集合被其他线程修改,就会抛出ConcurrentModificationException异常,解决fail-fast机制的方法包括使用普通for循环、Iterator

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);
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java获取当前操作系统的信息实例代码

    Java获取当前操作系统的信息实例代码

    这篇文章主要介绍了Java获取当前操作系统的信息实例代码,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • springboot分布式整合dubbo的方式

    springboot分布式整合dubbo的方式

    Dubbo是一款高性能、轻量级的开源Java RPC框架,本文通过实例代码给大家介绍springboot分布式整合dubbo的方式,感兴趣的朋友跟随小编一起看看吧
    2021-11-11
  • Java8新特性之方法引用的实践指南

    Java8新特性之方法引用的实践指南

    这篇文章主要给大家介绍了关于Java8新特性之方法引用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • maven配置阿里云仓库的实现方法

    maven配置阿里云仓库的实现方法

    本文主要介绍了maven配置阿里云仓库的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Spring中@RefreshScope注解的处理方法详解

    Spring中@RefreshScope注解的处理方法详解

    这篇文章主要介绍了Spring中@RefreshScope注解的处理方法详解,spring启动时会调用ClassPathBeanDefinitionScanner.java类中的doScan()对包路径下的所有class进行扫描,获取bean的定义,同时对bean的@RefreshScope(@Scope的父类)进行处理,需要的朋友可以参考下
    2023-10-10
  • Java实现调用jython执行python文件的方法

    Java实现调用jython执行python文件的方法

    这篇文章主要介绍了Java实现调用jython执行python文件的方法,结合实例形式分析了Java调用jython执行python文件的常见操作技巧及相关问题解决方法,需要的朋友可以参考下
    2018-03-03
  • springboot配置aop切面日志打印过程解析

    springboot配置aop切面日志打印过程解析

    这篇文章主要介绍了springboot配置aop切面日志打印过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • springboot创建多module项目的实例

    springboot创建多module项目的实例

    这篇文章主要介绍了springboot创建多module项目的实例代码,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Spring Boot两种配置文件properties和yml区别

    Spring Boot两种配置文件properties和yml区别

    这篇文章主要为大家介绍了java面试中常见问到的Spring Boot两种配置文件properties和yml区别解答,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • Java 读取文本指定的某一行内容的方法

    Java 读取文本指定的某一行内容的方法

    今天小编就为大家分享一篇Java 读取文本指定的某一行内容的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07

最新评论