Java 集合深入理解 :java.util 包的集合中 快速失败机制( fail-fast )
Posted 踩踩踩从踩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合深入理解 :java.util 包的集合中 快速失败机制( fail-fast )相关的知识,希望对你有一定的参考价值。
前言
快速失败机制是java集合中用来保证遍历过程中数据安全的机制 总的来说就是对比expectedModCount = modCount 判断数据操作,它并不判断数据是否有冲突的操作,而是看遍历时操作是否正确,依此达到数据安全。
for循环遍历并删除数据,但不抛出异常
我们可以区别一下下面的 通过for循环去删除数据这只是会导致数据删除不正确,原因是:会导致2和4未删除,list动态的变化了,遍历时也会随之变化
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
for(int i=0;i<list.size();i++) {
list.remove(i);
}
list.stream().forEach(i->{
System.out.println(i);
});
}
2
4
单线程抛出异常
在迭代器中边遍历边删除,会抛出ConcurrentModificationException异常
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
list.remove(3);
}
1
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 org.cao.test.Area.main(Area.java:40)
多线程中抛出异常
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
new Thread() {
@Override
public void run() {
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
try {
String s = iterator.next();
System.out.println( "thread1:" + s);
Thread.sleep(1000);
} catch (Exception e) {
System.out.println( "thread1 e:" + e.getMessage());
e.printStackTrace();
return;
}
};
super.run();
}
}.start();
new Thread() {
@Override
public void run() {
for(int i=0;i<list.size();i++) {
System.out.println("thread2:" + i);
list.remove(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
super.run();
}
}.start();
}
thread1:1
thread2:0
thread2:1
thread1 e:null
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at org.cao.test.Area$1.run(Area.java:47)
thread2:2
fail-fast 机制是什么
而fail-fast 机制是Java集合(Collection)中的一种错误机制。当多个线程(单线程也可能)对同一个集合的内容进行操作时,就可能会产生fail-fast(快速失败)事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
主要实现是来自通过 modCount 域保证数据的安全性,modCount 是用来记录此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表,或者以这样的方式对其进行扰动的
modCount 是什么
/**
*此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表,或者以这样的方式对其进行扰动
*进步可能会产生错误的结果。
*<p>此字段由迭代器和列表迭代器实现使用
*由{@CodeIterator}和{@CodeListIterator}方法返回。
*如果此字段的值意外更改,则迭代器(或列表迭代器)将在
*对{@code next}、{@code remove}、{@code previous}的响应,
*{@code set}或{@code add}操作。这提供了<i>快速失败</i>行为,而不是迭代过程中并发修改的情况。
*<p><b>子类使用此字段是可选的。</b>如果子类希望提供故障快速迭代器(和列表迭代器),那么只需在{@code add(int,E)}和
*{@code remove(int)}方法(以及它覆盖的任何其他方法)
*这将导致对列表进行结构性修改)。一通电话
*{@code add(int,E)}或{@code remove(int)}添加的内容不能超过
*一个到这个字段,或者迭代器(和列表迭代器)将抛出
*伪代码{@code ConcurrentModificationExceptions}。如果一个实现
*不希望提供失败快速迭代器,此字段可能是已忽略。
*/
protected transient int modCount = 0;
就是记录此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表,就是add remove等方法
arrayList中的迭代器
/**
* An optimized version of AbstractList.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();
}
}
迭代器初始化时
将这个值赋给迭代器的 expectedModCount;
并且初始化 cursor,主要用于记录刚刚遍历过的元素的索引
lastRet 最后一个元素的索引 删除则会设置为-1
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; // 返回的最后一个元素的索引-1如果没有
int expectedModCount = modCount;
hasnext方法
判断是否是最后一个数据,当数据迭代器迭代结束hasNext()返回会false
public boolean hasNext() {
return cursor != size;
}
next方法
在next方法中为了保证数据安全性
会用checkForComodification 方法 去校验 expectedModCount值和modCount是否一致。如果不一致则会抛出异常。
在遍历中 他会先检测if (i >= size) 检测i是否大约size抛异常。
并且判断 cursor 是否大于elementData.length 如果大于等于则 抛ConcurrentModificationException
@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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
迭代器中remove方法
会将expectedModCount = modCount; 会重新赋值
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();
}
}
在arraylist中对Itr做了一个扩展,包括ListItr 包括add set方法
在java提供的集合中在遍历过程中,使用快速失败来保证数据的安全。
并不保证保证多线程中的数据安全性,jdk在集合注释上都会建议 我们使用 Collections.synchronizedList 或者 java.util.concurrent 包下面的CopyOnWriteArrayList 或者ConcurrentHashMap等来保证多线程下数据安全性
以上是关于Java 集合深入理解 :java.util 包的集合中 快速失败机制( fail-fast )的主要内容,如果未能解决你的问题,请参考以下文章