Java中的fail-fast机制(基于JDK1.8)
来源:互联网 发布:plc有仿真软件 编辑:程序博客网 时间:2024/05/12 00:18
fail-fast简介
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生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>();
*
*/
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++;
}
}
}
}
第一次运行后,出现如下结果(在ThreadOne的代码中抛出异常)。
控制台输出:
0, 10,
0, 10, 11,
0, 10, 11, 12,
0, 10, 11, 12, 13,
0, 10, 11, 12, 13, 14,
Exception in thread "Thread-0" java.util.ConcurrentModificationException
0, 10, 11, 12, 13, 14, 15, at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.anyco.failfast.FastFailTest.printAll(FastFailTest.java:36)
at com.anyco.failfast.FastFailTest.access$300(FastFailTest.java:19)
at com.anyco.failfast.FastFailTest$ThreadOne.run(FastFailTest.java:49)
第二次运行后,出现如下结果(在ThreadTwo的代码中抛出异常)。
控制台输出:
Exception in thread "Thread-1" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
0, 0,
at java.util.ArrayList$Itr.next(ArrayList.java:851)
0, 1,
at com.anyco.failfast.FastFailTest.printAll(FastFailTest.java:36)
0, 1, 2,
at com.anyco.failfast.FastFailTest.access$300(FastFailTest.java:19)
0, 1, 2, 3,
at com.anyco.failfast.FastFailTest$ThreadTwo.run(FastFailTest.java:63)
0, 1, 2, 3, 4,
0, 1, 2, 3, 4, 5,
如果运行时没有出现异常,多运行几次。
结果说明:
在这里list是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,当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
为何产生ConcurrentModificationException
由于我们采用的是ArrayList做的测试,所以查看ArrayList源码即可,在上面抛出异常的输出中你会发现异常是出现在checkForComodification方法里面的,具体是在调用ArrayList$Itr.next方法的时候会调用checkForComodification方法,进而产生异常,那么这里的Itr又是什么呢?查看源码发现他是ArrayList的私有内部类,我们在调用ArrayList的iterator方法的时候会创建一个Itr对象出来:
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();
}
}
此句代码int expectedModCount = modCount;很重要,modCount是指list在结构上被改变的次数当然也包括list中内容顺序发生改变的次数,这个值的修改会使得迭代器产生不正确的结果,如果你查看ArrayList中modCount值哪里发生过修改的话,一般出现在add、remove、clear中。
在Itr的三种重要的方法中,都需要checkForComodification。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
由此可知,如果if (modCount != expectedModCount)成立,则会抛出ConcurrentModificationException的异常。expectedModCount的值是我们在开始迭代的时候赋值的,modCount有可能在我们迭代的过程中因为我们向ArrayList中添加或者删除元素而发生变化,这就会导致两者值不等了,也就是modCount != expectedModCount的情况了。
在最开始的demo中,我们用两个线程对同一个List进行操作,倘若在ThreadOne进行遍历list集合时,ThreadTwo对此对象进行了操作,则会导致
modCount != expectedModCount,因此也就抛出了ConcurrentModificationException的异常,ThreadTwo对list集合遍历时,抛出异常原理也如此。
如何解决fail-fast的问题
ArrayList是个线程不安全的类,我们可以通过使用concurrent包下的另一个类CopyOnWriteArrayList来替换它。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
CopyOnWriteArrayList与ArrayList的重要不同在于,CopyOnWriteArrayList没有继承AbstractList。
对于CopyOnWriteArrayList,其iterator遍历方式如下
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
@SuppressWarnings("unchecked")
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();
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
private final Object[] snapshot;用来保存要遍历的list的快照。
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
CopyOnWriteArrayList的底层实现仍然是一个数组,新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。
CopyOnWriteArrayList的并发问题
先看CopyOnWriteArrayList的几个重要的接口
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
毫无疑问CopyOnWriteArrayList在添加和remove元素的时候,都通过ReentrantLock加了锁,同一时刻对该list的添加、删除元素只能有一个线程在执行。
对public boolean add(E e)方法来说,同一时间只能有一个线程在添加元素。然后使用Arrays.copyOf(...)方法复制出另一个新的数组,而且新的数组的长度比原来数组的长度+1,副本复制完毕,新添加的元素也赋值添加完毕,最后又把新的副本数组赋值给了旧的数组,最后在finally语句块中将锁释放。
public E remove(int index),删除指定位置的元素,先判断要删除的元素是否最后一个,如果最后一个直接在复制副本数组的时候,复制长度为旧数组的length-1即可;但是如果不是最后一个元素,就先复制旧的数组的index前面元素到新数组中,然后再复制旧数组中index后面的元素到数组中,最后再把新数组复制给旧数组的引用。
最后在finally语句块中将锁释放。
CopyOnWriteArrayList的性能问题
优点:
1.解决的开发工作中的多线程的并发问题。
缺点:
1.内存占有问题:很明显,两个数组同时驻扎在内存中,如果实际应用中,数据比较多,而且比较大的情况下,占用内存会比较大,针对这个其实可以用ConcurrentHashMap来代替。
2.数据一致性:CopyOnWriteArrayList容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据,马上能读到,请不要使用CopyOnWriteArrayList容器,因为每次添加删除元素后,都需要经过一次拷贝数组的过程。
阅读全文
0 0
- Java中的fail-fast机制(基于JDK1.8)
- Java中的fail-fast机制
- java集合中的fail-fast机制
- java集合中的fail-fast机制
- java中的fail-fast(快速失败)机制
- Java fast-fail机制
- JAVA fail-fast机制
- Java fail-fast机制
- Java:fail-fast 机制
- Java集合(fail-fast机制)
- java fail-fast机制详解
- Java的fail-fast机制
- Java提高篇(三四)-----fail-fast机制
- Java提高篇(三四)-----fail-fast机制
- Java提高篇(三四)-----fail-fast机制
- Java提高篇(三四)-----fail-fast机制
- Java提高篇(三四)-----fail-fast机制
- Java提高篇(三四)-----fail-fast机制
- SHA256WithRSA签名算法(PHP实现)
- 机器学习(一)
- Ubuntu常用软件合集
- ReactNative 适配iPhoneX
- 复杂链表的复制
- Java中的fail-fast机制(基于JDK1.8)
- socket bind()编译出错
- mysql--12360处理null
- strcmp在不同系统/编译器下的结果不同
- 系统测试流程
- SSM项目——列表删除功能实现
- 浅谈单片机应用程序架构
- java基础(11)- java的输入/输出(IO)包(1)
- Android四大组件之Service