0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

单线程是否会引起 fail-fast机制

科技绿洲 来源:Java威廉希尔官方网站 指北 作者:Java威廉希尔官方网站 指北 2023-10-10 16:31 次阅读

fail-fast 是什么

引用百度百科的数据:

fail-fast 机制是 java 集合 (Collection) 中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件。

多线程?并发修改?才会引起 fail-fast 机制保护程序?小 B 觉得这个答案没有说全,面试官说了单线程也会引起 fail-fast 机制。那么关于单线程是否会引起 fail-fast 机制,百度百科说的对还是面试官说的对,写一个 demo 就清楚了。

import java.util.ArrayList;
import java.util.List;

public class FastFailTest {

    public static void main(String[] args) {
        List< String > list = new ArrayList< String >();

        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");

        for(String s : list) {
            if(s.equals("赵六")) {
                list.remove(s);
                System.out.println(list.toString());
            }
        }
    }
}

从下图的运行结果来看,list 已经完成了对 [赵六] 的 remove,说明并不是 remove 引发的问题,仔细查看异常原因:是在 ArrayList 的内部 Itr.checkForComodification() 方法出现的 ConcurrentModificationException 异常。小 B 感概了一句:网上资料不可尽信,动手实战才能出真知。

图片

原理

将异常定位到报错的 ArrayList.java:911 行。

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

可以看到这个方法 checkForComodification 对 modCount 和 expectedModCount 进行了比较,如果不相同就抛出异常。modCount 和 expectedModCount 分别又是什么呢?remove 方法中不是修改了 modCount 就是修改了 expectedModCount。

modCount 被定义在 ArrayList 的父类 AbstractList 中,每一次调用 Add、Remove、Clear 等方法 modCount 就被 +1,可以说明这个变量的作用就是记录了 ArrayList 实际被修改的次数。

ArrayList 的 foreach 方法是用迭代器 Iterator 实现的,Iterator 在 ArrayList 中有一个实现类:Itr,它的成员变量 expectedModCount 在初始化的时候被赋值了 modCount。所以当 ArrayList 调用 remove 删除元素时,modCount 被 +1,此时不等于 expectedModCount,在 foreach 试图将局部变量 s 交接给下一个元素的时候,就出现了 ConcurrentModificationException 异常。

图片

避免

经过分析,ConcurrentModificationException 是由于 modCount 和 expectedModCount 不一样导致的。

那么如何避免在循环的时候 add、remove 元素不抛出异常呢?

for 循环

使用普通的 for 循环,这样就可以不经过 Itr 内部类了。

List< String > list = new ArrayList< String >();

list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

for( int i = 0; i < list.size(); i++) {
    String s = list.get(i);
    if(s.equals("王五")) {
        list.remove(s);
        System.out.println(list.toString());
    }
}

示例结果是 [张三, 李四, 赵六] 没有出现异常。但是移除元素后面的索引已经被改变了。

迭代器 Iterator

直接使用迭代器 Iterator 中的方法,在它的remove 方法中显示的将 expectedModCount 赋值成 modCount。

//Itr.remove()
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

定义一个迭代器局部变量,使用 hasNext() 方法控制 while 循环。

List< String > list = new ArrayList< String >();

list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

Iterator< String > iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if(s.equals("王五")) {
        iterator.remove();
        System.out.println(list.toString());
    }
}

CopyOnWriteArrayList

CopyOnWriteArrayList 是 java 并发包 java.util.concurrent 下面的类。它在操作 add、remove 元素时,先将原来的元素数组拷贝一份成为新的数组,在新数组上面做元素操作,修改完成后,将 CopyOnWriteArrayList 中数组的引用指向了新数组。

List< String > list = new CopyOnWriteArrayList< String >();

list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

for(String s : list) {
    if(s.equals("李四")) {
        list.remove(s);
        System.out.println(list.toString());
    }
}

总结

fail-fast 机制就是不允许程序员不管是在单线程还是多线程环境中遍历集合的时候顺便还操作集合里面的元素。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2967

    浏览量

    104729
  • 程序
    +关注

    关注

    117

    文章

    3786

    浏览量

    81018
  • 单线程
    +关注

    关注

    0

    文章

    17

    浏览量

    1771
收藏 人收藏

    评论

    相关推荐

    单线程的双任务调度

    STM32是单线程的,通信协议层和应用功能层的耦合性比较低,如果独立运行,提高效率不少,主要实现的方法有哪些呢?
    发表于 01-10 10:15

    单线程SRAM静态内存使用

    概述本篇只要介绍这么使用STM32CubeMx工具添加RT-Thread操作系统组件,码代码的IDE是keil。介绍单线程SRAM静态内存使用。如果还不知道,这么使用STM32CubeMx工具添加
    发表于 08-24 06:57

    一种单线程编程思路简析

    事件驱动?逻辑控制?基于回调的事件驱动或者逻辑控制特点代码接口实现用法基于回调的事件驱动或者逻辑控制本文提供了一种单线程编程思路,并简单实现了该思路。受PLC编程线圈和触点概念的启发。将程序抽象理解
    发表于 02-16 06:58

    LabWindows_CVI多线程威廉希尔官方网站 的应用研究

    分析了线程与进程的关系,研究了LabWindows/CVI多线程威廉希尔官方网站 运行机制及其数据保护机制,对利用异步定时器实现的多线程软件与传统
    发表于 08-29 14:53 68次下载
    LabWindows_CVI多<b class='flag-5'>线程</b>威廉希尔官方网站
的应用研究

    线程好还是单线程好?单线程和多线程的区别 优缺点分析

    摘要:如今单线程与多线程已经得到普遍运用,那么到底多线程好还是单线程好呢?单线程和多线程的区别又
    发表于 12-08 09:33 8.1w次阅读

    从I/O的阻塞与非阻塞、I/O处理的单线程与多线程角度探讨服务器模型

    这里探讨的服务器模型主要指的是服务器端对I/O的处理模型。从不同维度可以有不同的分类,这里从I/O的阻塞与非阻塞、I/O处理的单线程与多线程角度探讨服务器模型。
    的头像 发表于 01-08 16:13 7000次阅读

    Intel处理器占据CPU单线程性能前17位 酷睿i9-9900KS仍稳居榜首

    目前,PassMark的CPU单线程性能图表仍然由Intel芯片主导。在AMD的Ryzen 9 PRO 3900前面,总共有17个Intel处理器占据了主导地位。
    发表于 04-09 14:39 2741次阅读
    Intel处理器占据CPU<b class='flag-5'>单线程</b>性能前17位 酷睿i9-9900KS仍稳居榜首

    实现Java多线程爬虫的两点

    在我们调试爬虫程序的时候,单线程爬虫没什么问题,但是当我们在线上环境使用单线程爬虫程序去采集网页时,单线程就暴露出了两个致命的问题:
    的头像 发表于 05-05 21:25 1961次阅读
    实现Java多<b class='flag-5'>线程</b>爬虫的两点

    这款16核怪物在单线程和多线程性能方面均跃居主流处理器榜首

    尽管AMD一段时间以来一直在主流芯片中注入更多的内核,但在单线程性能方面,这家芯片制造商的产品还不能与Intel的产品相提并论。如果这些PassMark号码准确无误,那么Zen 3似乎终于可以轻而易举地获得AMD的青睐。
    的头像 发表于 10-28 15:24 2095次阅读

    单线程也能开发异步任务?ACE JS框架到底是如何做到的

    ,用JS语言开发是否会导致硬件资源无法充分利用的情况呢? 本文给大家介绍“ACE JS的单线程异步机制”就是解决这个问题的。然而,说到 “单线程”与“异步”,大家可能会比较疑惑,因为
    的头像 发表于 08-13 17:16 2035次阅读
    <b class='flag-5'>单线程</b>也能开发异步任务?ACE JS框架到底是如何做到的

    JVM的垃圾机制是如何工作的呢?

    单线程收集器,“单线程” 的意义并不仅仅说明它只会使用一个 CPU 或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程 , 直到它收集结束。
    的头像 发表于 02-28 16:08 606次阅读

    Redis为何选择单线程

    Redis为何选择单线程? 在Redisv6.0以前,Redis的核心网络模型选择用单线程来实现。 核心意思就是,对于一个 DB 来说,CPU 通常不会是瓶颈,因为大多数请求不会是 CPU 密集型
    的头像 发表于 10-09 10:59 381次阅读

    Go在单线程计算性能上的优势

    一文中,我们讨论了Go在单线程计算性能上的优势。 现在,考虑这样的一种场景: 我们需要从某些网址中同步数据并进行计算,保存到本地redis缓存中。 现在,我们可以通过编写Go Worker的方式
    的头像 发表于 11-02 11:16 482次阅读
    Go在<b class='flag-5'>单线程</b>计算性能上的优势

    redis多线程还能保证线程安全吗

    Redis是一种使用C语言编写的高性能键值存储系统,它是单线程的,因为使用了多路复用的方式来处理并发请求。这样的实现方式带来了很好的性能,但同时也引发了一些线程安全方面的问题。 在Redis中,由于
    的头像 发表于 12-05 10:28 1809次阅读

    什么是多核多线程?多核多线程如何提高程序的运行效率?

    单线程无法充分利用多核处理器的并行计算能力。
    的头像 发表于 02-20 10:22 1353次阅读