快速失败机制--fail-fast
fail-fast 机制是Java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast(快速失败)事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
迭代器的快速失败行为无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常。
注意:上面所说的是在多线程环境下会发生fail-fast事件,但是单线程条件下如果违反了规则也是会产生fail-fast事件的
在文档中有这么一段话:编写的程序依赖于快速失败机制产生的异常是不对的,迭代器的快速检测机制仅仅用于检测错误。
分别用两段程序测试快速失败机制产生的原因:
单线程环境:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Created with IDEA
*
* @author DuzhenTong
* @Date 2018/3/18
* @Time 17:34
*/
public class FailFast {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
list.add(i);
}
iterator(list);
}
public static void iterator(List list) {
Iterator it = list.iterator();
int index = 0;
while (it.hasNext()) {
if (index == 6) {
list.remove(index);
}
index++;
System.out.println(it.next());
}
}
}
输出结果:
0
1
2
3
4
5
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
at java.util.ArrayList$Itr.next(ArrayList.java:791)
at FailFast.iterator(FailFast.java:29)
at FailFast.main(FailFast.java:18)
多线程环境:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Created with IDEA
*
* @author DuzhenTong
* @Date 2018/3/18
* @Time 17:59
*/
public class FailFast1 {
public static List list = new ArrayList();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
list.add(i);
}
new ThreadA().start();
new ThreadB().start();
}
public static class ThreadA extends Thread {
@Override
public void run() {
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.println("集合遍历中……:"+it.next());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class ThreadB extends Thread {
@Override
public void run() {
int index = 0;
while (index != 10) {
System.out.println("线程等待中……:"+index);
if (index == 3) {
list.remove(index);
}
index++;
}
}
}
}
输出结果:
线程等待中……:0
集合遍历中……:0
线程等待中……:1
线程等待中……:2
线程等待中……:3
线程等待中……:4
线程等待中……:5
线程等待中……:6
线程等待中……:7
线程等待中……:8
线程等待中……:9
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
at java.util.ArrayList$Itr.next(ArrayList.java:791)
at FailFast1$ThreadA.run(FailFast1.java:28)
上面的程序已经说明了为什么会发生fail-fast事件(快速失败),在多线程条件下,一个线程正在遍历集合中的元素,这时候另一个线程更改了集合的结构,程序才会抛出ConcurrentModificationException,在单线程条件下也是在遍历的时候,这时候更改集合的结构,程序就会抛出ConcurrentModificationException。
要具体知道为什么会出现fail-fast,就要分析源码,fail-fast出现是在遍历集合的时候出现的,也就是对集合进行迭代的时候,对集合进行迭代的时候都是操作迭代器,集合中的内部类:(ArrayList源码)
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = modCount;//---------------------1
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
//………此处代码省略…………
}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
上面的代码中我们可以看到抛出ConcurrentModificationException异常,上面的next(),remove()都会调用checkForComodification()方法检查两个变量的值是否相等,不相等就会抛出异常。在上面程序中的数字1处:
int expectedModCount = modCount;
modCount的值赋值给expectedModCount,知道modCount这个值的含义是什么?为什么会发生改变?原因就会找到了。
源码点进去,发现这个modCount变量并不在ArrayList类中,而在AbstractList中,作为一个成员变量。
protected transient int modCount = 0;
接下来分析源码,看最常用的方法
ArrayList中add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
调用的ensureCapacityInternal方法:
private void ensureCapacityInternal(int minCapacity) {
modCount++;//modCount自增————————————
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ArrayList中的remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;//modCount自增——————————————
E oldValue = 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;
}
ArrayList中的clear方法:
public void clear() {
modCount++;//modCount自增——————————————
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
分析了源码就知道原因是什么了,凡是涉及到改变了集合的结构(改变元素的个数)的操作(包括增加,移除或者清空等)modCount这个变量都会自增,在获得迭代对象的时候,先把这个modCount变量赋值给expectedModCount,在迭代的时候每次都会检查这个变量是否与expectedModCount一致,因为如果是在集合中添加或者删除元素modCount的值都会发生改变。
解决方法:
- 对于涉及到更改集合中元素个数的操作通通加上synchronized,或者利用Collections.synchronizedList强制他们的操作都是同步的。
- 使用CopyOnWriteArrayList来替换ArrayList
这里在网上看到很多的文章都是这么说的:为什么CopyOnWriteArrayList可以做到不会发生fail-fast?因为CopyOnWriteArrayList所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。
可以分析源码(下面的1,2处)
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();//————————1
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//————————2
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
我的看法:原因不止有CopyOnWriteArrayList的add、set、remove等会改变原数组的方法中,都是先copy一份原来的array,再在copy数组上进行add、set、remove操作,这就才不影响COWIterator那份数组。
为什么没有记录修改次数的值或者说不比较modCount也能做到内存的一致性呢?
在上面的代码1处,调用了getArray()方法,看源码:
final Object[] getArray() {
return array;
}
private volatile transient Object[] array;
因为getArray()返回的array的类型是用volatile修饰的,volatile类型的(强制内存一致性)
具体可以看我的另一篇关于volatile的:volatile关键字解析
参考文章:http://cmsblogs.com/?p=1220
快速失败机制--fail-fast的更多相关文章
- 快速失败(fail—fast)和 安全失败(fail—safe)
快速失败(fail-fast) 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加.删除),则会抛出Concurrent Modification Exception. 原理 ...
- Java集合框架中的快速失败(fail—fast)机制
fail-fast机制,即快速失败机制,是java集合框架中的一种错误检测机制.多线程下用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加.删除),则会抛出Concurre ...
- 关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错
一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...
- windows系统的快速失败机制---fastfail
windows系统的快速失败机制---fastfail,是一种用于“快速失败”请求的机制 — 一种潜在破坏进程请求立即终止进程的方法. 无法使用常规异常处理设施处理可能已破坏程序状态和堆栈至无法恢复的 ...
- java中的fail-fast(快速失败)机制
java中的fail-fast(快速失败)机制 简介 fail-fast机制,即快速失败机制,是java集合中的一种错误检测机制.当在迭代集合的过程中对该集合的结构改变是,就有可能会发生fail-fa ...
- 【原创】快速失败机制&失败安全机制
这是why技术的第29篇原创文章 之前在写<这道Java基础题真的有坑!我求求你,认真思考后再回答.>这篇文章时,我在8.1小节提到了快速失败和失败安全机制. 但是我发现当我搜索" ...
- 面试笔记--Fast-Fail(快速失败)机制
1.解决: fail-fast机制,是一种错误检测机制.它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生.若在多线程环境下使用fail-fast机制的集合,建议使用“java. ...
- Java的快速失败和安全失败
文章转自https://www.cnblogs.com/ygj0930/p/6543350.html 一:快速失败(fail—fast) 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进 ...
- 面试题思考:java中快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
一:快速失败(fail—fast) 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加.删除.修改),则会抛出Concurrent Modification Exceptio ...
随机推荐
- windows与虚拟机linux能ping通设置
作为以后参考所用. 首先,介绍如何在VMWare中设置linux的网络.一般网络选项有Bridged,NAT,host-only几种,本次以host-only作详细说明,如下图: 在选择host-on ...
- 1_类的定义(Defining Class)
C++ 提供了一种类class机制,让程序员可以定义真正意义上的数据类型.即不但可以定义数据的复合,还可以定义该复合数据的操作,以便让本应由使用该数据类型的程序员做得工作分出来,让定义类型的程序员去做 ...
- DAVINCI DM6446 开发攻略——V4L2视频驱动和应用分析
针对DAVINCI DM6446平台,网络上也有很多网友写了V4L2的驱动,但只是解析Montavista linux-2.6.10 V4L2的原理.结构和函数,深度不够.本文决定把Montavis ...
- 把mmapv1存储引擎存储的mongodb3.0数据库数据复制到WiredTiger存储引擎的mongodb3.2中
mongodb3.0在mmapv1的存储引擎基础上添加了一个新的存储引擎WiredTiger.但是3.0的默认存储引擎依旧是mmapv1,因此我们项目之前也就用的默认方式. 但是mongodb更新实在 ...
- 从DataTable中查询数据
/// <summary> /// 从DataTable中查询数据 /// </summary> /// <param name="tb">待处 ...
- Good Bye 2017 D. New Year and Arbitrary Arrangement
看了别人的题解 首先这题是一个dp dp[i][j] i是当前有多少个a j是当前有多少个ab子序列 dp[i][j] = dp[i+1][j]*Pa + dp[i][i+j]*Pb; i,j 时加一 ...
- jQuery对象与js对象互相转换
两种转换方式将一个jQuery对象转换成js对象:[index]和.get(index); (1)jQuery对象是一个数据对象,可以通过[index]的方法,来得到相应的js对象. 如:var $v ...
- freemarker获取封装类中对象的属性(六)
freemarker获取封装类中对象的属性 1.设计思路 (1)封装学生类 (2)创建数据模型 (3)新建student.ftl (4)运行Junit测试文件,生成HTML文件 2.封装学生类 Stu ...
- JavaScript设计模式(5)-组合模式
组合模式 1. 适合使用组合模式的条件: 存在一批组织成某种层次体系的对象,如树形结构(具体的结构在开发期间可能无法得知) 希望对这批对象或其中的一部分对象实施一个相同的操作 2. 注意点: 组合对象 ...
- Android破解学习之路(七)—— 乐秀视频编辑 内购破解 专业版 价值25元的破解
按照之前的支付宝破解,搜索9000的十六进制,之后... 但是,这样测试的时候,没有破解成功,便是继续研究 搜索关键字支付失败,之后找到了指定的smali文件,观察了许久,发现里面有个switch跳转 ...