java中的fail-fast(快速失败)机制

简介

  fail-fast机制,即快速失败机制,是java集合中的一种错误检测机制。当在迭代集合的过程中对该集合的结构改变是,就有可能会发生fail-fast,即跑出ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定抛出异常,它只是近最大努力去抛出,所以这种机制一般仅用于检测bug

fail-fast的出现场景

在我们常见的java集合中就可能出现fail-fast机制,比如常见的ArrayList,HashMap.在多线程和单线程环境下都有可能出现快速失败

1.单线程环境下的fail-fast例子:
    public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
list.remove(3);
}
System.out.println(iterator.next());
i ++;
}
}

控制台打印:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.example.springboot_demo.fail_fast_safe.ArrayListFailFast.main(ArrayListFailFast.java:24)

该段代码定义了一个Arraylist集合,并使用迭代器遍历,在遍历过程中,刻意在某一步迭代中remove一个元素,这个时候,就会发生fail-fast

2.多线程环境下
public class ArrayListFailFastThreadPool {
public static List<String> list = new ArrayList<>();

private static class MyThread1 extends Thread {
@Override
public void run() {
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String s = iterator.next();
System.out.println(this.getName() + ":" + s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
super.run();
}
}

private static class MyThread2 extends Thread {
int i = 0;
@Override
public void run() {
while (i < 10) {
System.out.println("thread2:" + i);
if (i == 2) {
list.remove(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i ++;
}
}
}

public static void main(String[] args) {
for(int i = 0 ; i < 10;i++){
list.add(i+"");
}
MyThread1 thread1 = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread1.setName("thread1");
thread2.setName("thread2");
thread1.start();
thread2.start();
}

}

控制台打印:

Exception in thread "thread1" java.util.ConcurrentModificationException
thread2:3
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.example.springboot_demo.fail_fast_safe.ArrayListFailFastThreadPool$MyThread1.run(ArrayListFailFastThreadPool.java:20)

启动两个线程,分别对其中一个对list进行迭代,另一个在线程1的迭代过程中去remove一个元素,结果也是抛出了java.util.ConcurrentModificationException

Fail-fast原理

fail-fast是如何抛出ConcurrentModificationException异常的,又是在什么情况下才会抛出?

我们知道,对于集合入list,map类,我们都是可以通过迭代器来遍历,而Iterator其实只是一个接口,具体的实现还是具体的集合类的内部类实现Iterator并实现相关方法。这里我们就以ArrayList类为例。在ArrayList中,当调用list.iterator()时,其源码是:

 public Iterator<E> iterator() {
return new Itr();
} private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

public boolean hasNext() {
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
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];
}

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

@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
} //这段代码是关键
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
     可以看出,该方法才是判断是否抛出ConcurrentModificationException异常的关键。在该段代码中,当modCount != expectedModCount时,就会抛出该异常。很明显expectedModCount在整个迭代过程除了一开始赋予初始值modCount外,并没有再发生改变,所以可能发生改变的就只有modCount.
在前面关于ArrayList扩容机制的分析中,可以知道在ArrayList进行add,remove,clear等涉及到修改集合中的元素个数的操作时,modCount就会发生改变(modCount++)所以当另一个线程(并发修改)或者同一个线程遍历过程中,调用相关方法使集合的个数发生改变,就会使modCount发生变化,这样在checkForComodification方法中就会抛出ConcurrentModificationException异常。
类似的,hashMap中发生的原理也是一样的。
避免fail-fast

方法1

在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法

 public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
iterator.remove(); //迭代器的remove()方法
}
System.out.println(iterator.next());
i ++;
}
}

方法2

  使用java并发包(java.util.concurrent)中的类来代替ArrayList 和hashMap。

CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList几乎一样,CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

对于HashMap,可以使用ConcurrentHashMap,ConcurrentHashMap采用了锁机制,是线程安全的。在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。

java中的fail-fast(快速失败)机制的更多相关文章

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

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

  2. 快速失败机制--fail-fast

    fail-fast 机制是Java集合(Collection)中的一种错误机制.当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast(快速失败)事件.例如:当某一个线程A通过iter ...

  3. 【原创】快速失败机制&失败安全机制

    这是why技术的第29篇原创文章 之前在写<这道Java基础题真的有坑!我求求你,认真思考后再回答.>这篇文章时,我在8.1小节提到了快速失败和失败安全机制. 但是我发现当我搜索" ...

  4. windows系统的快速失败机制---fastfail

    windows系统的快速失败机制---fastfail,是一种用于“快速失败”请求的机制 — 一种潜在破坏进程请求立即终止进程的方法. 无法使用常规异常处理设施处理可能已破坏程序状态和堆栈至无法恢复的 ...

  5. 关于JAVA中事件分发和监听机制实现的代码实例-绝对原创实用

    http://blog.csdn.net/5iasp/article/details/37054171 文章标题:关于JAVA中事件分发和监听机制实现的代码实例 文章地址: http://blog.c ...

  6. Java基础-Java中的内存分配与回收机制

    Java基础-Java中的内存分配与回收机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一. 二.

  7. Java中内存泄露及垃圾回收机制

    转自:http://blog.sina.com.cn/s/blog_538b279a0100098d.html 写的相当不错滴...................... 摘  要 Java语言中,内 ...

  8. AQS:Java 中悲观锁的底层实现机制

    介绍 AQS AQS(AbstractQueuedSynchronizer)是 Java 并发包中,实现各种同步组件的基础.比如 各种锁:ReentrantLock.ReadWriteLock.Sta ...

  9. Java并发编程:Java中的锁和线程同步机制

    锁的基础知识 锁的类型 锁从宏观上分类,只分为两种:悲观锁与乐观锁. 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新 ...

随机推荐

  1. Linux常用命令操作文档

    Ls命令:列出目录内容 选项 长选项 含义 -a --all 列出所有文件,包括隐藏的文件 -d --directory 指定一个目录 -F --classify 在每个列出的名字后面加上类型指示符( ...

  2. 快速部署业务类为webapi服务

    接着前一篇博文,将接口快速打包固定请求格式,不需要修改代码,可以自动完成接口调用,实际上就是生成了一个接口的代理类. 那么仅仅是接口请求代理,没有服务端怎么行?所以需要将实现接口的类部署为webapi ...

  3. Git相关命令整理

    git config --global user.name  //配置姓名git config --global user.email  //配置邮箱git config --list  //查看配置 ...

  4. 找回git rebase --skip消失的代码

    1.git reflog操作,查看提交的历史记录,找到自己的提交 2.强制回退到上一次提交:git reset --hard  791a1fc 或者 git reset --hard  HEAD@{2 ...

  5. CMS(1)

    一周后,终于可以学习到可爱的渗透了哈哈哈.除了大哥给的CMS(其实可以算是只是在文件上传的时候了解一下),但是对于一个CMS完整的渗透思路,我还是不懂.首先感谢章老师给我的CMS源码哈哈哈,在我的日记 ...

  6. psfstriptable - 从控制台字体中移走嵌入的Uniocde字符表

    总览 psfstriptable 字体文件 [输出文件] 描述 psfstriptable 命令从 字体文件 或者标准输入(此时的 字体文件 是单个破折号(-))读取一个可能含有嵌入Unicode字体 ...

  7. 解析天启rk3288源码 /kernel/drivers/char/virtd

    virtd为编译后产生的中间文件,可使用ELF格式逆向 1.ELF文件内容解析readelf: 可解析ELF文件的所有内容;strings: 查看ELF文件中的字符串;file   : 查看ELF文件 ...

  8. nginx下TP3.2访问页面总是404

    这是我测试onethink用的配置 可以参考一下 server { listen 80; server_name onethink.dev www.onethink.dev; root /vagran ...

  9. Splay平衡树入门小结

    学习到这部分算是数据结构比较难的部分了,平衡树不好理解代码量大,但在某些情况下确实是不可替代的,所以还是非学不可. 建议先学Treap之后在学Splay,因为其实Splay有不少操作和Treap差不多 ...

  10. idea 查看类图

    快捷键CTRL+H查看 利用idea快捷键查看hierarchy,效果如下 好处:可以看向上和向下的继承关系 缺点:只能看继承关系,不能看实现了哪些接口 在指定类右键查看diagram 也可以使用快捷 ...