惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
博客园 - 【当耐特】
Cyberwarzone
Cyberwarzone
B
Blog
U
Unit 42
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
美团技术团队
Hugging Face - Blog
Hugging Face - Blog
大猫的无限游戏
大猫的无限游戏
博客园 - 聂微东
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 叶小钗
F
Full Disclosure
Microsoft Azure Blog
Microsoft Azure Blog
Recorded Future
Recorded Future
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
云风的 BLOG
云风的 BLOG
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
F
Fortinet All Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Cisco Blogs
The Hacker News
The Hacker News
Know Your Adversary
Know Your Adversary
S
Securelist
S
Schneier on Security
P
Privacy International News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
AWS News Blog
AWS News Blog
J
Java Code Geeks
T
The Exploit Database - CXSecurity.com
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
C
Cybersecurity and Infrastructure Security Agency CISA
H
Heimdal Security Blog
博客园 - Franky
P
Palo Alto Networks Blog
PCI Perspectives
PCI Perspectives
T
Tailwind CSS Blog
IT之家
IT之家
Engineering at Meta
Engineering at Meta
Latest news
Latest news
P
Proofpoint News Feed
量子位
Apple Machine Learning Research
Apple Machine Learning Research
K
Kaspersky official blog

技术改变视野

为程序员、设计师打造的摸鱼社区:摸鱼派! - 技术改变视野 大白话 | Java初学指♂男:“说说初学的误区与死结”( 壹 | 反射与Field ) - 技术改变视野 大白话之耦合性:什么是耦合性和内聚性?用编程语言实例讲解! - 技术改变视野 🎨 适用于 Solo / Bolo 的清新高级主题 —— Sakura | 移植自 WordPress 主题 - 技术改变视野 我在 GitHub 上的开源项目 - 技术改变视野 为IDEA/Git配置GPG密钥,点亮GitHub Verified Commit标识 - 技术改变视野 从 SpringMVC 转为使用 SpringBoot —— 手把手带你使用 Intellij IDEA 最快速地搭建 SpringBoot+MyBatis(无 XML)+Thymeleaf - 技术改变视野 无题 - 技术改变视野 年轻人的第一辆京牌电动车 - 技术改变视野 我们忙碌且漫长的一生,有多久是为自己而活? - 技术改变视野 一个最简单的实例理解Semaphore在Java中的作用 - 技术改变视野 说说《后浪》 - 技术改变视野 离线使用 Google Fonts:让你的网站更精致 | goofoffline 神器,一键下载生成离线字体库! - 技术改变视野 实例带你学会简单的Java Thread多线程 - 技术改变视野 一个实例理解Java的接口(interface)用处与用法 - 技术改变视野 一个实例带你理解JavaBean - 技术改变视野 WebFilter-SpringBoot过滤器注解实例讲解 - 技术改变视野 实例带你搞懂Java多线程&&线程池之(壹):线程池与多线程的关系和区别 - 技术改变视野 实例带你搞懂Java多线程&&线程池之(贰):简单的线程池应用 - 技术改变视野 实例带你获取多线程Thread的返回值之 (壹) - Callable的运行 - 技术改变视野 实例带你获取多线程Thread的返回值之 (贰) - Callable配合线程池返回数据 - 技术改变视野 一个实例理解Java Runnable多线程用处与用法 - 技术改变视野 从零开始OpenSSL之 (壹) - 使用genrsa、rsa、rsautl生成公私钥 - 技术改变视野 从零开始 OpenSSL 之 (贰) - 使用 rsautl 解密文件 - 技术改变视野 大白话之Docker(壹):快速入门&&简单官方实例 - 技术改变视野 大白话之Docker(贰):简单部署一个Tomcat服务并发布内容 - 技术改变视野 大白话之从零讲解DVWA(壹)-SQL注入(SQL Injection) Low Level - 技术改变视野 大白话之从零讲解DVWA(贰)-SQL注入(SQL Injection) Medium/High Level - 技术改变视野 Java日志插件-Slog4J下载 - 技术改变视野 大白话之必会Java Atomic | 线程一点也不安全(一):比自增和synchronized更快速、靠谱的原子操作(调用C语言) - 技术改变视野 Log-MySQL root用户登录后无法查看数据库全部表/正常访问数据库 Access denied for user 'root'@'localhost' to database - 技术改变视野 大白话之Docker(叁):制作一个运行Tomcat服务端的Docker镜像 - 技术改变视野 Github仅保留指定文件/文件夹当前Commit,删除所有历史记录,清除「敏感信息」 - 技术改变视野 将Tomcat、MySQL从Linux迁移到Windows的心路历程(干货):令人恐惧的字符编码 - 技术改变视野 Spring: java.lang.NoSuchMethodError: clearCache | ClassNotFoundException | Error during artifact deployment 思路及解决办法 - 技术改变视野 大白话之Java反射-初学最迷的概念:能干啥?咋用? - 技术改变视野 大白话之Java面向接口编程:最“正经”的中文实例讲解,看不懂来打我! - 技术改变视野 大白话 | 课堂实践:使用Iterator对数组进行遍历 - 技术改变视野 刨根问底 | 大白话:在使用注解后,框架是怎么知道你哪个方法使用了注解的?用@RequestMapping注解举例详解! - 技术改变视野 大白话之必会Java Atomic | 线程一点也不安全(二):Atomic的ABA问题会导致什么情况?如何解决? - 技术改变视野 随笔 | 奇淫技巧 | Java:记return和短路运算符的妙用 - 技术改变视野 随笔 | Tomcat:续-从Linux迁移到Windows编码问题彻底解决 - 技术改变视野 大白话之AutoClosable接口 | try-with-resources:JDK1.7的新特性,提高重复回收效率 - 技术改变视野 大白话之Java多线程join方法:开局一张图,试学一分钟,你就费和我一样,理解介个方法 - 技术改变视野 大白话之Java Stream流:将类数组流化,便捷批量修改,通俗讲解! - 技术改变视野 Java 困扰三周の问题:使用byte[]或skip()方法读取字节流Stream文件尾部多/少/缺字节解决方法 - 技术改变视野 大白话之Java级联调用:一个类,一条语句,同时调用好几个方法,串成一串 - 技术改变视野
大白话之fail-fast | fail-safe:为什么会有这个机制?它有什么作用? - 技术改变视野
adlered · 2019-12-09 · via 技术改变视野

前言

阅读本篇文章,你需要了解下列知识:

  • 多线程的实现(看过来
  • Iterator的使用
  • ArrayList的使用和如何实现Iterator

为什么会有这个机制?

举个栗子

  1. 有一杯水、两个人(黄渤和红雷)
  2. 黄渤拿起了水杯,开始喝水
  3. 红雷到达案发现场,想走水杯喝水
  4. 黄渤很生气,并锤了红雷一顿

映射关系

将上面的栗子翻译一下:

  1. 有一个ArrayList、两个线程(Thread1Thread2
  2. Thread1请求并开始使用Iterator遍历ArrayList
  3. Thread2随后紧跟请求对ArrayList进行修改
  4. 由于Thread1正在遍历ArrayListArrayListThread2扔出ConcurrentModificationException

继承关系

Tip 如果下面的知识让你难以搞懂,可以直接跳过,在深入学习接口和继承后,再回来看一遍。

6.png

看起来有些困难?没关系。

1ArrayList继承了Iterable接口,实现了基于Iterator的遍历功能。

ArrayList实现Iterator的部分源码如下:

 1    /**
 2     * An optimized version of AbstractList.Itr
 3     */
 4    private class Itr implements Iterator<E> {
 5        int cursor;       // index of next element to return
 6        int lastRet = -1; // index of last element returned; -1 if no such
 7        int expectedModCount = modCount;
 8
 9        // prevent creating a synthetic constructor
10        Itr() {}
11
12        public boolean hasNext() {
13            return cursor != size;
14        }
15
16        @SuppressWarnings("unchecked")
17        public E next() {
18            checkForComodification();
19            int i = cursor;
20            if (i >= size)
21                throw new NoSuchElementException();
22            Object[] elementData = ArrayList.this.elementData;
23            if (i >= elementData.length)
24                throw new ConcurrentModificationException();
25            cursor = i + 1;
26            return (E) elementData[lastRet = i];
27        }
28
29        public void remove() {
30            if (lastRet < 0)
31                throw new IllegalStateException();
32            checkForComodification();
33
34            try {
35                ArrayList.this.remove(lastRet);
36                cursor = lastRet;
37                lastRet = -1;
38                expectedModCount = modCount;
39            } catch (IndexOutOfBoundsException ex) {
40                throw new ConcurrentModificationException();
41            }
42        }
43
44        @Override
45        public void forEachRemaining(Consumer<? super E> action) {
46            Objects.requireNonNull(action);
47            final int size = ArrayList.this.size;
48            int i = cursor;
49            if (i < size) {
50                final Object[] es = elementData;
51                if (i >= es.length)
52                    throw new ConcurrentModificationException();
53                for (; i < size && modCount == expectedModCount; i++)
54                    action.accept(elementAt(es, i));
55                // update once at end to reduce heap write traffic
56                cursor = i;
57                lastRet = i - 1;
58                checkForComodification();
59            }
60        }
61
62        final void checkForComodification() {
63            if (modCount != expectedModCount)
64                throw new ConcurrentModificationException();
65        }
66    }

throw new ConcurrentModificationException();Itr类中被赋予了四次条件执行:

方法 字段 触发条件
next() if (i >= elementData.length) throw new ConcurrentModificationException(); 当枚举的指针大于等于ArrayList的长度时,抛出错误
remove() try {ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException(); } 当删除ArrayList中指定对象时出错(对象不存在),抛出错误
forEachRemaining() if (i >= es.length) throw new ConcurrentModificationException(); 当枚举的指针大于等于ArrayList的长度时,抛出错误
checkForComodification() if (modCount != expectedModCount) throw new ConcurrentModificationException(); 检测当运行中统计对象的数量不等于预计中对象的数量时,抛出错误

它有什么作用?

当多个线程同时对一个对象进行高频率的增删改查时,可能会出现数据异常。

实例

例子很简单,我们创建两个线程,Thread1访问修改ArrayListThread2Thread1尚未访问完毕时同时对ArrayList进行访问修改:

打开你的IDE,新建类Main.java并复制下方代码:

 1import java.text.SimpleDateFormat;
 2import java.util.ArrayList;
 3import java.util.Date;
 4import java.util.Iterator;
 5import java.util.List;
 6
 7public class Main {
 8    //需要操作的ArrayList
 9    private static List arrayList = new ArrayList<>();
10
11    public static void main(String[] args) {
12        //将类Thr实例化为两个线程
13        Thread thread1 = new Thr();
14        Thread thread2 = new Thr();
15        //同时启动两个线程进行操作
16        thread1.start();
17        thread2.start();
18    }
19
20    private static void printAll(String threadName) {
21        System.out.println("由线程" + threadName + "遍历后,当前ArrayList的内容:");
22        //获取ArrayList的Iterator枚举器
23        Iterator iterator = arrayList.iterator();
24        //将ArrayList中全部内容遍历并打印
25        while(iterator.hasNext()) {
26            System.out.print(iterator.next());
27        }
28        System.out.println();
29    }
30
31    static class Thr extends Thread {
32        public void run() {
33            System.out.println("线程" + Thread.currentThread().getName() + "开始运行!当前时间" + new SimpleDateFormat("mm🇸🇸SS").format(new Date()));
34            //将0-5写入到ArrayList中,每次写入打印一次ArrayList中全部的内容
35            for (int i = 0; i < 5; i++) {
36                //将i写入到ArrayList
37                arrayList.add(i);
38                //打印ArrayList中全部的内容
39                printAll(Thread.currentThread().getName());
40            }
41        }
42    }
43
44}

运行结果:

 1线程Thread-0开始运行!当前时间14:04:358
 2线程Thread-1开始运行!当前时间14:04:359
 3由线程Thread-1遍历后,当前ArrayList的内容:
 40由线程Thread-0遍历后,当前ArrayList的内容:
 500
 6由线程Thread-0遍历后,当前ArrayList的内容:
 7001
 8由线程Thread-0遍历后,当前ArrayList的内容:
 90012
10由线程Thread-0遍历后,当前ArrayList的内容:
110Exception in thread "Thread-1" 00123
12由线程Thread-0遍历后,当前ArrayList的内容:
130java.util.ConcurrentModificationException
1401234
15	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
16	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
17	at failfast.Main.printAll(Main.java:28)
18	at failfast.Main$Thr.run(Main.java:41)

请注意 你的运行结果可能和我的运行结果会有以下两种偏差:

  1. 两个线程的启动顺序、启动时间
  2. 可能不会产生报错(由于两个线程读取ArrayList的时间恰巧错开)

你可能发现了一个规律:

结果是随机的,最重要的是ConcurrentModificationException是不稳定的。即使两个线程同时访问,也有一部分可能性不会抛出ConcurrentModificationException。

为什么呢?让我们看看官方对fail-fast机制的解释:

下方解释部分来源于https://www.cnblogs.com/kubidemanong/articles/9113820.html

迭代器的fail-fast行为是不一定能够得到保证的。一般来说,存在非同步的并发修改时,是不能够保证错误一定被抛出的。但是会做出最大的努力来抛出ConcurrentModificationException

因此,编写依赖于此异常的程序的做法是不正确的。正确的做法应该是:迭代器的fail-fast行为应该仅用于检测程序中的Bug。

避免fail-fast

ArrayList使用fail-fast机制自然是因为它增强了数据的安全性。但在某些场景,我们可能想避免fail-fast机制产生的错误,这时我们就要将ArrayList替换为使用fail-safe机制的CopyOnWriteArrayList

将:

1private static List arrayList = new ArrayList<>();

修改为:

1private static List arrayList = new CopyOnWriteArrayList<>();

CopyOnWriteArrayListIterator的实现上没有设计抛出ConcurrentModificationException的代码段,所以便避免了fail-fast机制错误的抛出。我们将它称之为fail-safe机制

后语

本篇篇幅较长,涉及的知识点较乱。如果你有问题,可以在下方评论中提出;如果你发现本篇文章中存在误导内容,也请在评论中及时告知,谢谢!