fail-fast机制简介

什么是fail-fast

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

这种“ 及时失败” 的迭代器井不是一种完备的处理机制,而只是“ 善意地” 捕获并发错误,因此只能作为并发问题的预警指示器。

fail-fast示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class FailFastTest {

    static final List<Integer> list = new ArrayList<>();

    public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(() -> {
add((int)(Math.random() * 10));
print();
});
}
} private static void add(int number) {
list.add(number);
} private static void print() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

以上代码会抛出java.util.ConcurrentModificationException异常

fail-fast的解决办法

使用java.util.concurrent.*下的工具类

深入ArrayList源码看fast-fail的原理

先放上
首先看下ArrayList中的内部类Itr的域

1
2
3
4
5
private class Itr implements Iterator<E> {
int cursor; //1
int lastRet = -1; //2
int expectedModCount = modCount; //3
}
  1. 表示迭代器下一个元素的索引;
  2. 表示迭代器上一个元素的索引;
  3. 在创建一个迭代器时,将当前ArrayList的修改次数赋值给expectedModCount保存。

在上面的示例中,我们可以看到一般的迭代过程是

1
2
3
4
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}

分别看下iterator()、hasNext()、next()三个方法:

  • iterator():没有做任何处理,不过构造时三个域会进行初始化
1
Itr() {}
  • hasNext():判断下一个元素索引是否等于ArrayList的大小,等于说明没有元素了
1
2
3
public boolean hasNext() {
return cursor != size;
}
  • 接下来重点看next()方法
1
2
3
4
5
6
7
8
9
10
11
public E next() {
checkForComodification(); //1
int i = cursor; //2
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

在获取下一个元素之前,先调用checkForComodification()进行了检查,检查当前集合的修改次数是不是跟之前保存的相同,如果相同则表示没有被其他线程修改,

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

modCount:modCount 是 AbstractList 的属性值:`protected transient int modCount = 0; 他是一个修改次数计数器,实例化一个集合之后,每次修改(源码的注释成为结构性修改),比如set,add,clear等,计数器都会加1。

这里其实就是fail-fast机制的实现原理了,将修改计数器的变化与容器关联起来:首先在构造迭代器的时候,将当前的修改计数器的值保存,之后进行遍历的时候,每访问一个数据,都要检查当前集合的修改次数是否合法,如果有其他线程修改了集合,那么modCount就会被修改,当前修改计数器的值与之前保存的值(即期望值)不同,那么将抛出ConcurrentModificationException。

fail-fast解决办法
        通过前面的实例、源码分析,我想各位已经基本了解了fail-fast的机制,下面我就产生的原因提出解决方案。这里有两种解决方案:

方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

方案二:使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。

CopyOnWriteArrayList为何物?ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。 该类产生的开销比较大,但是在两种情况下,它非常适合使用。1:在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。2:当遍历操作的数量大大超过可变操作的数量时。遇到这两种情况使用CopyOnWriteArrayList来替代ArrayList再适合不过了。

关于CopyOnWriteArrayList请看相关文章

深入ArrayList看fast-fail机制的更多相关文章

  1. NIO 源码分析(04) 从 SelectorProvider 看 JDK SPI 机制

    目录 一.SelectorProvider SPI 二.SelectorProvider 加载过程 2.1 SelectorProvider 加载 2.2 Windows 下 DefaultSelec ...

  2. Java集合框架中的快速失败(fail—fast)机制

      fail-fast机制,即快速失败机制,是java集合框架中的一种错误检测机制.多线程下用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加.删除),则会抛出Concurre ...

  3. Storm ack和fail机制再论

    转自:http://www.cnblogs.com/fxjwind/p/3806404.html 之前对这个的理解有些问题,今天用到有仔细梳理了一遍,记录一下 首先开启storm tracker机制的 ...

  4. 关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错

    一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...

  5. HDFS深度历险 之 从客户端逻辑看HDFS写入机制

    说明 除了标注之外,本文纯属原创,转载请注明出处:https://www.jianshu.com/p/ea6ef5f5b868, https://www.cnblogs.com/monkeyteng/ ...

  6. 源码看React 事件机制

    对React熟悉的同学都知道,React中的事件机制并不是原生的那一套,事件没有绑定在原生DOM上,发出的事件也是对原生事件的包装.那么这一切是怎么实现的呢? 事件注册 首先还是看我们熟悉的代码 &l ...

  7. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  8. 从e.getMessage()为null看Java异常机制

    问题:自定义异常触发了,但是自定义的提示信息RuntimeException却没有带过来. throw new RuntimeException("不允许插入报价主项和报价子项同时重复的记录 ...

  9. java源码学习(四)ArrayList

    ArrayList ​ ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ​ ArrayList不是线程安全的,只能用在单线程环境下, ...

随机推荐

  1. hdu 6214 : Smallest Minimum Cut 【网络流】

    题目链接 ISAP写法 #include <bits/stdc++.h> using namespace std; typedef long long LL; namespace Fast ...

  2. System.currentTimeMillis和System.nanoTime()

    ns(nanosecond):纳秒, 时间单位.一秒的10亿分之一,即等于10的负9次方秒.常用作 内存读写速度的单位.  1纳秒=0.000001 毫秒  1纳秒=0.00000 0001秒 jav ...

  3. 倍增求LCA算法详解

    算法介绍: 看到lca问题(不知道lca是什么自(bang)行(ni)百度),不难想到暴力的方法: 先把两点处理到同一深度,再让两点一个一个祖先往上找,直到找到一个相同的祖先: 这么暴力的话,时间复杂 ...

  4. 特征提取算法(1)——纹理特征提取算法LBP

    模式识别中进行匹配识别或者分类器分类识别时,判断的依据就是图像特征.用提取的特征表示整幅图像内容,根据特征匹配或者分类图像目标. 常见的特征提取算法主要分为以下3类: 基于颜色特征:如颜色直方图.颜色 ...

  5. es之IK分词器

    1:默认的分析器-- standard 使用默认的分词器 curl -XGET 'http://hadoop01:9200/_analyze?pretty&analyzer=standard' ...

  6. [转]Vue项目全局配置微信分享思路详解

    这篇文章给大家介绍了vue项目全局配置微信分享思路讲解,使用vue作为框架,使用vux作为ui组件库,具体内容详情大家跟随脚本之家小编一起学习吧 这个项目为移动端项目,主要用于接入公众号服务.项目采用 ...

  7. nginx 入门 安装

    .yum解决编译nginx所需的依赖包,之后你的nginx就不会报错了 yum install gcc patch libffi-devel python-devel zlib-devel bzip2 ...

  8. Exchanger 源码分析

    Exchanger 此类提供对外的操作是同步的: 用于成对出现的线程之间交换数据[主场景]: 可以视作双向的同步队列: 可应用于基因算法.流水线设计.数据校对等场景 创建实例 /** * arena ...

  9. try...catch语句

    程序的异常:Throwable 严重问题Error我们不处理,这种问题一般都是很严重的,比如内存溢出 问题Exception 编译期问题不是RuntimeException的异常必须进行处理,如果不处 ...

  10. c# Thread——1.为什么Abort中断线程是不可靠的

    Thread.Abort 方法在c#中用作强制中断线程的执行,大多用于线程内部满足某个特定条件而自己调用关闭自身,比如下面的代码在i自增到3的时候就会停止打印. class Program { sta ...