系列文章:
前言
fail-fast機制是Java集合的一種錯誤機制,當多個線程對同一集合內(nèi)容操作時,便可能會出現(xiàn)fail-fast現(xiàn)象。例如:存在兩個線程(A、B),A通過Iterator在遍歷集合C中的元素過程中,B修改了集合C的內(nèi)容,程序就可能會拋出 ConcurrentModificationException 異常,出現(xiàn)fail-fast現(xiàn)象。
本文源碼分析基于jdk 1.8.0_121
實例展示
下面給出一個實際代碼展示,來演示fail-fast現(xiàn)象,如下:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Main {
private static List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
for (int i= 0; i<5; i++){
list.add(i);
}
new ThreadA().start();
new ThreadB().start();
}
static class ThreadA extends Thread{
public void run(){
Iterator iter = list.iterator();
int value = 0;
while(iter.hasNext()) {
value = (int)iter.next();
System.out.println(value+", ");
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
static class ThreadB extends Thread{
public void run(){
for (int i = 5;i<10;i++){
list.add(i);
}
}
}
}
運行以上代碼,結(jié)果如下:
0,
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at Main$ThreadA.run(Main.java:22)
需要注意的是,如果我們再加一個子線程C,其代碼如下:
static class ThreadC extends Thread{
public void run(){
for (int i = 0;i<5;i++){
list.set(i,i+1);
}
}
}
new ThreadA().start();
new ThreadC().start();
當我們同時運行子線程A和子線程C時,程序可以順利執(zhí)行,因為子線程沒有改變原有list的結(jié)構(gòu),只是重新給每個索引設(shè)置了新的值,運行結(jié)果如下:
0,
2,
3,
4,
5,
解決辦法
對于前面的集合ArrayList其不是線程安全的,LinkedList也不是線程安全的,在多線程的環(huán)境下,我們可以使用java.util.concurrent包下的類去取代java.util包下的類來避免并發(fā)帶來的fail-fast現(xiàn)象;對于ArrayList我們可以使用CopyOnWriteArrayList代替即可。
fail-fast原理
當操作Iterator時,如果拋出ConcurrentModificationException便出現(xiàn)了fail-fast現(xiàn)象,而ArrayList的iterator方法返回一個Itr對象,Itr類是ArrayList的內(nèi)部類:
// Itr是迭代器的實現(xiàn)類
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
// 修改次數(shù)的記錄值
// 遍歷list元素時,會比較expectedModCount和modCount是否相等
// 如果不相等,則會拋出異常,出現(xiàn)fail-fast現(xiàn)象
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 判斷expectedModCount和modCount是否相等
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();
}
}
modCount是AbstractList中的一個成員變量,而ArrayList繼承自AbstractList,ArrayList的以下方法會修改modCount:
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
...
從以上我們可以發(fā)現(xiàn),當涉及到修改ArrayList中元素個數(shù)的方法(remove(),clear(),ensureExplicitCapacity()...),便會修改modCount。
因此在前文演示代碼中,子線程A正在通過迭代器遍歷集合元素,而子線程B中add()方法修改了集合中元素的數(shù)量,導(dǎo)致此時expectedModCount和modCount不相等,因此執(zhí)行checkForComodification方法時,便會拋出異常。
CopyOnWriteArrayList源碼分析
前文說了CopyOnWriteArrayList可以解決集合面對并發(fā),下面我們從源碼角度分析下CopyOnWriteArrayList:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
...
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
...
static final class COWIterator<E> implements ListIterator<E> {
// 創(chuàng)建COWIterator時,將集合中的元素保存到一個新的數(shù)組
// 當原始集合的數(shù)據(jù)改變,拷貝數(shù)據(jù)中的值也不會變化
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();
}
...
}
}
從以上代碼,我們可以看到以下幾點:
-
CopyOnWriteArrayList沒有繼承AbstractList,只是實現(xiàn)了List接口 -
ArrayList返回的Iterator是在AbstractList中實現(xiàn)的,而CopyOnWriteArrayList是自己實現(xiàn)的Iterator -
CopyOnWriteArrayList的各個方法中不會拋出ConcurrentModificationException異常