fail-fast机制—高级用法与深入解读

前言

细心地朋友看 Java 容器源码时一定会发现在list()和listIterator()的注释中都有一句话:

The iterators returned by this class’s iterator and listIterator methods are fail-fast.

我看ArrayList源码没认真想fail-fast是什么意思,看Vector源码时又看到了这个词,而且在翻看Set实现类和Map实现类源码时也看到了这个词。fail-fast是什么?本篇文章以Vector为例来详细解说fail-fast。

what’s the “fail-fast”?

下面是Vector中源码的最上部的注释中关于fail-fast的介绍:

原文

The iterators returned by this class's {@link #iterator() iterator} and
 * {@link #listIterator(int) listIterator} methods are <em>fail-fast</em></a>:
 * if the vector is structurally modified at any time after the iterator is
 * created, in any way except through the iterator's own
 * {@link ListIterator#remove() remove} or
 * {@link ListIterator#add(Object) add} methods, the iterator will throw a
 * {@link ConcurrentModificationException}.  Thus, in the face of
 * concurrent modification, the iterator fails quickly and cleanly, rather
 * than risking arbitrary, non-deterministic behavior at an undetermined
 * time in the future.  The {@link Enumeration Enumerations} returned by
 * the {@link #elements() elements} method are <em>not</em> fail-fast.
 *
 * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed
 * as it is, generally speaking, impossible to make any hard guarantees in the
 * presence of unsynchronized concurrent modification.  Fail-fast iterators
 * throw {@code ConcurrentModificationException} on a best-effort basis.
 * Therefore, it would be wrong to write a program that depended on this
 * exception for its correctness:  <i>the fail-fast behavior of iterators
 * should be used only to detect bugs.</i>

翻译

由iterator()和listIterator()返回的迭代器是fail-fast的。在于程序在对list进行迭代时,某个线程对该collection在结构上对其做了修改,这时迭代器就会抛出ConcurrentModificationException异常信息。因此,面对 并发的修改,迭代器快速而干净利落地失败,而不是在不确定的情况下冒险。由elements()返回的Enumerations不是fail-fast的。需要注意的是,迭代器的fail-fast并不能得到保证,它不能够保证一定出现该错误。一般来说,fail-fast会尽最大努力抛出ConcurrentModificationException异常。因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。

大意为在遍历一个集合时,当集合结构被修改,很有可能会抛出Concurrent Modification Exception。为什么说是很有可能呢?从下文中我们可以知道,迭代器的remove操作(注意是迭代器的remove方法而不是集合的remove方法)修改集合结构就不会导致这个异常。

看到这里我们就明白了,fail-fast 机制是java容器(Collection和Map都存在fail-fast机制)中的一种错误机制。在遍历一个容器对象时,当容器结构被修改,很有可能会抛出ConcurrentModificationException,产生fail-fast。

再理解

大概意思是:在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。

其实就是在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报,比如下面的这个简单的例子:

/**这里的代码是一个对两个整数做除法的方法,
*在fast_fail_method方法中,我们对被除数做了个简单的检查
*如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。
*这其实就是fail-fast理念的实际应用。
*/

public int fast_fail_method(int arg1,int arg2){
    if(arg2 == 0){
        throw new RuntimeException("can't be zero");
    }
    return arg1/arg2;
}

在Java集合类中很多地方都用到了该机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会触发该机制时,之后就会抛出并发修改异常ConcurrentModificationException.当然如果不在多线程环境下,如果在foreach遍历的时候使用add/remove方法,也可能会抛出该异常。

什么时候会出现fail-fast?

在以下两种情况下会导致fail-fast,抛出ConcurrentModificationException

单线程环境

遍历一个集合过程中,集合结构被修改。注意,listIterator.remove()方法修改集合结构不会抛出这个异常。

多线程环境

当一个线程遍历集合过程中,而另一个线程对集合结构进行了修改。

单线程环境例子

import java.util.ListIterator;
import java.util.Vector;

public class Test {
/**
     * 单线程测试
     */

    @org.junit.Test
    public void test() 
{
        try {
            // 测试迭代器的remove方法修改集合结构会不会触发checkForComodification异常
            ItrRemoveTest();
            System.out.println("----分割线----");
            // 测试集合的remove方法修改集合结构会不会触发checkForComodification异常
            ListRemoveTest();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 测试迭代器的remove方法修改集合结构会不会触发checkForComodification异常
    private void ItrRemoveTest() {
        Vector list = new Vector<>();
        list.add("1");
        list.add("2");
        list.add("3");
        ListIterator itr = list.listIterator();
        while (itr.hasNext()) {
            System.out.println(itr.next());
            //迭代器的remove方法修改集合结构
            itr.remove();
        }
    }

    // 测试集合的remove方法修改集合结构会不会触发checkForComodification异常
    private void ListRemoveTest() {
        Vector list = new Vector<>();
        list.add("1");
        list.add("2");
        list.add("3");
        ListIterator itr = list.listIterator();
        while (itr.hasNext()) {
            System.out.println(itr.next());
            //集合的remove方法修改集合结构
            list.remove("3");
        }
    }
}

运行结果

1
2
3
----分割线----
1
java.util.ConcurrentModificationException
    at java.util.Vector$Itr.checkForComodification(Unknown Source)

从结果中可以看到迭代器itr的remove操作并没有出现ConcurrentModificationException异常。而集合的remove操作则产生了异常。

多线程环境例子

import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;

public class Test {
    private static List<String> list = new Vector<String>();

    /**
     * 多线程情况测试
     */

    @org.junit.Test
    public void test2() 
{
        list.add("1");
        list.add("2");
        list.add("3");
        // 同时启动两个线程对list进行操作!
        new ErgodicThread().start();
        new ModifyThread().start();
    }

    /**
     * 遍历集合的线程
     */

    private static class ErgodicThread extends Thread {
        public void run() {
            int i = 0;
            while (i < 10) {
                printAll();
                i++;
            }
        }
    }

    /**
     * 修改集合的线程
     */

    private static class ModifyThread extends Thread {
        public void run() {
            list.add(String.valueOf("5"));
        }
    }
    /**
     * 遍历集合
     */

    private static void printAll() {
        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            System.out.print((String) iter.next() + ", ");
        }
        System.out.println();
    }
}

运行结果

123
123
123
1, Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.Vector$Itr.checkForComodification(Unknown Source)

从结果中可以看出当一个线程遍历集合,而另一个线程对这个集合的结构进行了修改,确实有可能触发ConcurrentModificationException异常。

fail-fast实现原理

下面是Vector中迭代器Itr的部分源码

/**
 * An optimized version of AbstractList.Itr
 */

private class Itr implements Iterator<E{
    int expectedModCount = modCount;

    //省略的部分代码

    public void remove() {
        if (lastRet == -1)
            throw new IllegalStateException();
        synchronized (Vector.this) {
            checkForComodification();
            Vector.this.remove(lastRet);
            expectedModCount = modCount;
        }
        cursor = lastRet;
        lastRet = -1;
    }

    @Override
    public void forEachRemaining(Consumer<? super E> action) {
            //省略的部分代码
            checkForComodification();
        }
    }

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

从代码中可以看到,每次初始化一个迭代器都会执行int expectedModCount = modCount;。modcount意为moderate count,即修改次数,对集合内容的修改都将增大这个值,如modCount++;。在迭代器初始化过程中会执行int expectedModCount = modCount;来记录迭会通过checkForComodification()方法判断modCount和expectedModCount 是否相等,如果不相等就表示已经有线程修改了集合结构。

使用迭代器的remove()方法修改集合结构不会触发ConcurrentModificationException,现在可以在源码中看出来是为什么。在remove()方法的最后会执行expectedModCount = modCount;,这样itr.remove操作后modCount和expectedModCount依然相等,就不会触发ConcurrentModificationException了。

如何避免fail-fast?

使用java.util.concurrent包下的类去取代java.util包下的类。所以,本例中只需要将Vector替换成java.util.concurrent包下对应的类即可。

import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;

public class Test 
    /**
     * CopyOnWriteArrayList的fail-fast测试
     */

    @org.junit.Test
    public void test3() 
{
        try {
            List list = new CopyOnWriteArrayList<>();
            list.add("1");
            list.add("2");
            list.add("3");
            ListIterator itr = list.listIterator();
            while (itr.hasNext()) {
                System.out.println(itr.next());
                list.add("5");
                list.remove("2");
            }
            System.out.println(list.toString());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

1
2
3
[13555]

从运行结果中不难发现,在遍历过程中,使用集合的remove()方法修改集合结构并没有产生ConcurrentModificationException。


原文始发于微信公众号(步尔斯特): fail-fast机制—高级用法与深入解读

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

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35857.html

(0)
小半的头像小半
0 0

相关推荐

  • 微服务从代码到k8s部署应有尽有系列(四、用户中心) 后端开发

    微服务从代码到k8s部署应有尽有系列(四、用户中心)

    0 0121
    小半的头像 小半
    2022年9月26日
  • 【382期】京东一面:子线程如何获取父线程ThreadLocal的值 面试题

    【382期】京东一面:子线程如何获取父线程ThreadLocal的值

    0 0209
    小半的头像 小半
    2022年5月17日
  • 面试官突然的关心-日志篇 并发编程

    面试官突然的关心-日志篇

    0 0208
    小半的头像 小半
    2022年8月1日
  • 10道不得不会的ElasticSearch面试题 Java

    10道不得不会的ElasticSearch面试题

    0 0374
    小半的头像 小半
    2022年7月22日
  • 【SpringBoot学习】6、SpringBoot 整合 Mybatis 逆向工程 解决方案 Java自学入门

    【SpringBoot学习】6、SpringBoot 整合 Mybatis 逆向工程 解决方案

    0 0134
    小半的头像 小半
    2023年1月4日
  • 强烈推荐10款IDEA插件,大大提高工作效率 后端漫谈

    强烈推荐10款IDEA插件,大大提高工作效率

    0 0232
    Java朝阳的头像 Java朝阳
    2024年3月29日
  • java基础之数据类型及变量 Java

    java基础之数据类型及变量

    0 0141
    小半的头像 小半
    2022年10月9日
  • Java从零开始(25)Java String 类 Java自学入门

    Java从零开始(25)Java String 类

    0 0151
    小半的头像 小半
    2022年6月2日
  • 25. 会话技术-Cookie的使用 后端开发

    25. 会话技术-Cookie的使用

    0 0275
    小半的头像 小半
    2022年9月27日
  • Linux命令拾遗-入门篇 微信精选

    Linux命令拾遗-入门篇

    0 0202
    小半的头像 小半
    2023年11月6日
  • 你真的会用volatile吗 后端开发

    你真的会用volatile吗

    0 0129
    小半的头像 小半
    2022年7月25日
  • 一张图帮你看懂,在浏览器输入网址回车后,都发生了什么? Java

    一张图帮你看懂,在浏览器输入网址回车后,都发生了什么?

    0 0107
    Java朝阳的头像 Java朝阳
    2024年4月2日

发表回复

登录后才能评论

扫码关注公众号,技术文章第一时间送达

fail-fast机制—高级用法与深入解读

站长精选

  • 如何用 Nginx 代理 MySQL 连接,并限制可访问IP?

    如何用 Nginx 代理 MySQL 连接,并限制可访问IP?

    2023年7月26日

  • Spring Event 业务解耦神器,大大提高可扩展性,刷爆了!

    Spring Event 业务解耦神器,大大提高可扩展性,刷爆了!

    2023年10月11日

  • ELK 处理 SpringBoot 日志,真是太妙了!

    ELK 处理 SpringBoot 日志,真是太妙了!

    2023年9月23日

  • 13 秒插入 30 万条数据,这才是 Java 批量插入正确的姿势!

    13 秒插入 30 万条数据,这才是 Java 批量插入正确的姿势!

    2024年3月18日

  • SpringCloud 分布式系统中实现幂等性的几种方式

    SpringCloud 分布式系统中实现幂等性的几种方式

    2023年9月23日

  • 这才是企业级的oss-spring-boot-starter,开箱即用!

    这才是企业级的oss-spring-boot-starter,开箱即用!

    2024年3月3日

  • 实现一个基于注解的 Excel 万能导出模板

    实现一个基于注解的 Excel 万能导出模板

    2023年4月1日

  • RabbitMQ 消息丢失的场景,如何保证消息不丢失?

    RabbitMQ 消息丢失的场景,如何保证消息不丢失?

    2023年3月16日

  • 小心,高并发系统居然被一行日志击垮?

    小心,高并发系统居然被一行日志击垮?

    2022年11月15日

  • 8000字 + 25图探秘 xxl-job 核心架构原理

    8000字 + 25图探秘 xxl-job 核心架构原理

    2023年12月3日

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!

玻璃钢生产厂家肇庆公仔玻璃钢雕塑辽宁景观玻璃钢雕塑报价浮雕龙玻璃钢雕塑广东景观玻璃钢雕塑销售电话大庆玻璃钢雕塑生产厂家佛像玻璃钢雕塑设计公司广州景区玻璃钢雕塑定制广西玻璃钢泡沫雕塑公司九江佛像玻璃钢雕塑联系方式信阳佛像玻璃钢卡通雕塑天津商场主题创意商业美陈怎么样特色商场美陈销售公司泉州玻璃钢花盆花器黄冈玻璃钢雕塑厂家凯蒂猫玻璃钢雕塑辉南玻璃钢雕塑厂家玻璃钢雕塑雕塑工程宣威市玻璃钢雕塑设计代理芜湖设计玻璃钢雕塑报价多彩玻璃钢雕塑定制公仔玻璃钢卡通雕塑订做通用玻璃钢雕塑摆件制造丰都玻璃钢景观雕塑岳阳市玻璃钢雕塑广州玻璃钢仿铜雕塑公园玻璃钢几何雕塑售价宁夏玻璃钢雕塑价格正圆玻璃钢雕塑搜狐号重庆玻璃钢龙雕塑厦门小区玻璃钢雕塑安装香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化