转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308762.html

fail-fast简介(使用concurrentHashMap可以完美避免这个问题)

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

ConcurrentModificationException异常,产生fail-fast事件。在详细介绍fail-fast机制的原理之前,先通过一个示例来认识fail-fast。

fail-fast示例
import java.util.*;
import java.util.concurrent.*;
/*
* @desc java集合中Fast-Fail的测试程序。
*
* fast-fail事件产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。
* fast-fail解决办法:通过util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。
*
* 本例中,分别测试ArrayList和CopyOnWriteArrayList这两种情况。ArrayList会产生fast-fail事件,而CopyOnWriteArrayList不会产生fast-fail事件。
* (01) 使用ArrayList时,会产生fast-fail事件,抛出ConcurrentModificationException异常;定义如下:
* private static List<String> list = new ArrayList<String>();
* (02) 使用时CopyOnWriteArrayList,不会产生fast-fail事件;定义如下:
* private static List<String> list = new CopyOnWriteArrayList<String>();
*
* @author skywang
*/
public class FastFailTest { private static List<String> list = new ArrayList<String>();
//private static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
// 同时启动两个线程对list进行操作!
new ThreadOne().start();
new ThreadTwo().start();
} private static void printAll() {
System.out.println(""); String value = null;
Iterator iter = list.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
} /**
* 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list
*/
private static class ThreadOne extends Thread {
public void run() {
int i = 0;
while (i<6) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
} /**
* 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list
*/
private static class ThreadTwo extends Thread {
public void run() {
int i = 10;
while (i<16) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}
}

运行结果:运行该代码,抛出异常java.util.ConcurrentModificationException!即,产生fail-fast事件!

结果说明

(01) FastFailTest中通过 new ThreadOne().start() 和 new ThreadTwo().start() 同时启动两个线程去操作list。

ThreadOne线程:向list中依次添加0,1,2,3,4,5。每添加一个数之后,就通过printAll()遍历整个list。

ThreadTwo线程:向list中依次添加10,11,12,13,14,15。每添加一个数之后,就通过printAll()遍历整个list。

(02) 当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了;就会抛出ConcurrentModificationException异常,产生fail-fast事件。

fail-fast解决办法

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。所以,本例中只需要将ArrayList替换成java.util.concurrent包下对应的类即可。即,将代码

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

替换为

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

则可以解决该办法。

fail-fast原理

产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。那么,ArrayList是如何抛出ConcurrentModificationException异常的呢?

我们知道,ConcurrentModificationException是在操作Iterator时抛出的异常。我们先看看Iterator的源码。ArrayList的Iterator是在父类AbstractList.java中实现的。代码如下:

package java.util;

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    ...

    // AbstractList中唯一的属性
// 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1
protected transient int modCount = 0; // 返回List对应迭代器。实际上,是返回Itr对象。
public Iterator<E> iterator() {
return new Itr();
} // Itr是Iterator(迭代器)的实现类
private class Itr implements Iterator<E> {
int cursor = 0; int lastRet = -1; // 修改数的记录值。
// 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;
// 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;
// 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size();
} public E next() {
// 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;
// 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
} public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification(); try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
} ...
}

从中,我们可以发现在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。

要搞明白 fail-fast机制,我们就要需要理解什么时候“modCount 不等于

expectedModCount”!从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。

接下来,我们查看ArrayList的源码,来看看modCount是如何被修改的。

package java.util;

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{ ... // list中容量变化时,对应的同步函数
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
} // 添加元素到队列最后
public boolean add(E e) {
// 修改modCount
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} // 添加元素到指定的位置
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size); // 修改modCount
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
} // 添加集合
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
// 修改modCount
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
} // 删除指定位置的元素
public E remove(int index) {
RangeCheck(index); // 修改modCount
modCount++;
E oldValue = (E) elementData[index]; int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // Let gc do its work return oldValue;
} // 快速删除指定位置的元素
private void fastRemove(int index) { // 修改modCount
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
} // 清空集合
public void clear() {
// 修改modCount
modCount++; // Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
} ...
}

从中,我们发现:无论是add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值。

接下来,我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:

(01) 新建了一个ArrayList,名称为arrayList。

(02) 向arrayList中添加内容。

(03) 新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值。(04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。

(05) 这时,就会产生有趣的事件了。 在某一时刻,“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。

在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1!“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比

较“expectedModCount”和“modCount”的大小;

而“expectedModCount=N”,“modCount=N+1”,这样,便抛出

ConcurrentModificationException异常,产生fail-fast事件。

至此,我们就完全了解了fail-fast是如何产生的!即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出

ConcurrentModificationException异常,产生fail-fast事件。

解决fail-fast的原理

上面,说明了“解决fail-fast机制的办法”,也知道了“fail-fast产生的根本原因”。接下来,我们再进一步谈谈java.util.concurrent包中是如何解决fail-fast事件的。还是以和ArrayList对应的CopyOnWriteArrayList进行说明。我们先看看CopyOnWriteArrayList的源码:

package java.util.concurrent;
import java.util.*;
import java.util.concurrent.locks.*;
import sun.misc.Unsafe; public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... // 返回集合对应的迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
} ... private static class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot; private int cursor; private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
// 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。
// 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。
snapshot = elements;
} public boolean hasNext() {
return cursor < snapshot.length;
} public boolean hasPrevious() {
return cursor > 0;
} public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
} public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
} public int nextIndex() {
return cursor;
} public int previousIndex() {
return cursor-1;
} public void remove() {
throw new UnsupportedOperationException();
} public void set(E e) {
throw new UnsupportedOperationException();
} public void add(E e) {
throw new UnsupportedOperationException();
}
}
...
}

从中,我们可以看出:

(01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。

(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。

(03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出

ConcurrentModificationException异常!

作者:lintong
链接:https://www.jianshu.com/p/fb824b3cf987
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

fail-fast简介的更多相关文章

  1. Fail Fast and Fail Safe Iterators in Java

    https://www.geeksforgeeks.org/fail-fast-fail-safe-iterators-java/ Fail Fast and Fail Safe Iterators ...

  2. fail fast和fail safe策略

    优先考虑出现异常的场景,当程序出现异常的时候,直接抛出异常,随后程序终止 import java.util.ArrayList; import java.util.Collections; impor ...

  3. 【问题】Could not locate PropertySource and the fail fast property is set, failing

    这是我遇到的问题 Could not locate PropertySource and the fail fast property is set, failing springcloud的其他服务 ...

  4. 快速失败(fail—fast)和 安全失败(fail—safe)

    快速失败(fail-fast) 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加.删除),则会抛出Concurrent Modification Exception. 原理 ...

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

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

  6. JStorm之Nimbus简介

    本文导读: ——JStorm之Nimbus简介 .简介 .系统框架与原理 .实现逻辑和代码剖析 )Nimbus启动 )Topology提交 )任务调度 )任务监控 .结束语 .参考文献 附:JStor ...

  7. Java集合容器简介

    Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...

  8. 关于TOCTTOU攻击的简介

    前言 最近看到了一些以 at 结尾的Linux系统调用,在维基百科上面说这可以防御一些特定的TOCTTOU攻击,而在TOCTTOU对应页面中并没有中文版的介绍,而且百度的结果也比较少,于是决定抽空写一 ...

  9. [转载]Java集合容器简介

    Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...

  10. hystrix简介

    hystrix,框架,提供了高可用相关的各种各样的功能,然后确保说在hystrix的保护下,整个系统可以长期处于高可用的状态,100%. 高可用系统架构: 资源隔离.限流.熔断.降级.运维监控 资源隔 ...

随机推荐

  1. js原生判断数字类型

    js判断数字类型汇总最近在写代码的时候,有些逻辑需要判断数字类型,等用到的时候才发现自己了解的方法不太严密,然后就决心查资料汇总了解下有哪些方法比较严密 第一种:typeof + isNaN使用typ ...

  2. 基于DFA算法实现的敏感词过滤

    本文转自浅析敏感词过滤算法(C++),自己也在其基础上根据自己的情况做了一点修改. https://blog.csdn.net/u012755940/article/details/51689401? ...

  3. SQL数学函数学习

    笔记来自如鹏网杨中科老师所著的 <程序员的SQL金典>#创建数据库表 CREATE Table T_Person ( FIdNumber VARCHAR(20), FName varcha ...

  4. mysql-canal-kafka-kettle 数据实时同步链部署bug 填坑过程

    1,因为 mysql 版本从5.7 提高到 8.0  ,需要更改用户配置. create user 'canal'@'%' identified by 'canal';grant select , r ...

  5. Go组件库总结之无等待锁

    本篇文章我们用Go封装一个无等待锁库.文章参考自:https://github.com/brewlin/net-protocol 1.锁的封装 type Mutex struct { v int32 ...

  6. 6.navicat for mysql 定时计划 定时导出EXCEL文件 并 附件发送邮件到固定邮箱

    链接:https://blog.csdn.net/pizilii/article/details/80829761 补充: 先查看mysql定时任务是否开启. show variables like ...

  7. Xcode 12.x下载了iOS10.x模拟器无法创建对应Device问题修复

    转自: https://hiraku.tw/2021/04/6428/ 感谢原作者,如有侵权请评论联系删除文章 在升級到 Xcode 12 之後,有些人發現雖然 Xcode 允許安裝低版本的模擬器,但 ...

  8. API对象--Ingress(chrono《kubernetes入门实战课》笔记整理)

    [概念说明] Service 的功能和运行机制,主要由 kube-proxy 控制的四层负载均衡,即根据IP.PORT来做负载均衡.而很多应用都是在7层做均衡更为合理,比如根据主机名.URI.请求头. ...

  9. [部署日记]Android Studio在同步Gradle的时候提示Could not resolve com.android.tools.build:gradle:7.0.4

    一波未平一波又起,好家伙直呼好家伙. 问题来源: 在想好APP起什么名字,放哪里后,打开模拟器准备着手处理时发现, Gradle sync出问题了... "Build"窗口提示: ...

  10. Eclipse 获取maven项目出现问题汇总

    1.errors occurred during the build,可以试试加上下图的配置,路径根据自己jak安装路径去修改