系列文章:
- Java集合系列01之概览
- Java集合系列02之ArrayList源码分析
- Java集合系列03之LinkedList源码分析
- Java集合系列04之fail-fast机制分析
- Java集合系列05之Vector&Stack源码分析及List总结
- Java集合系列06之Map接口概览
- Java集合系列07之HashMap源码分析
- Java集合系列08之WeakHashMap源码分析
- Java集合系列09之TreeMap源码分析
- Java集合系列10之Hashtable源码分析
前言
fail-fast
机制是Java集合的一种错误机制,当多个线程对同一集合内容操作时,便可能会出现fail-fast
现象。例如:存在两个线程(A、B),A通过Iterator
在遍历集合C中的元素过程中,B修改了集合C的内容,程序就可能会抛出 ConcurrentModificationException
异常,出现fail-fast
现象。
本文源码分析基于jdk 1.8.0_121
实例展示
下面给出一个实际代码展示,来演示fail-fast
现象,如下:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Main {
private static List 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);
}
}
}
}
运行以上代码,结果如下:
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时,程序可以顺利执行,因为子线程没有改变原有list
的结构,只是重新给每个索引设置了新的值,运行结果如下:
0,
2,
3,
4,
5,
解决办法
对于前面的集合ArrayList
其不是线程安全的,LinkedList
也不是线程安全的,在多线程的环境下,我们可以使用java.util.concurrent
包下的类去取代java.util
包下的类来避免并发带来的fail-fast
现象;对于ArrayList
我们可以使用CopyOnWriteArrayList
代替即可。
fail-fast原理
当操作Iterator
时,如果抛出ConcurrentModificationException
便出现了fail-fast
现象,而ArrayList
的iterator
方法返回一个Itr
对象,Itr
类是ArrayList
的内部类:
// Itr是迭代器的实现类
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
// 修改次数的记录值
// 遍历list元素时,会比较expectedModCount和modCount是否相等
// 如果不相等,则会抛出异常,出现fail-fast现象
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;
}
...
从以上我们可以发现,当涉及到修改ArrayList
中元素个数的方法(remove(),clear(),ensureExplicitCapacity()...)
,便会修改modCount
。
因此在前文演示代码中,子线程A正在通过迭代器遍历集合元素,而子线程B中add()
方法修改了集合中元素的数量,导致此时expectedModCount
和modCount
不相等,因此执行checkForComodification
方法时,便会抛出异常。
CopyOnWriteArrayList源码分析
前文说了CopyOnWriteArrayList
可以解决集合面对并发,下面我们从源码角度分析下CopyOnWriteArrayList
:
public class CopyOnWriteArrayList
implements List, RandomAccess, Cloneable, java.io.Serializable {
...
public Iterator iterator() {
return new COWIterator(getArray(), 0);
}
...
static final class COWIterator implements ListIterator {
// 创建COWIterator时,将集合中的元素保存到一个新的数组
// 当原始集合的数据改变,拷贝数据中的值也不会变化
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
,只是实现了List
接口 -
ArrayList
返回的Iterator
是在AbstractList
中实现的,而CopyOnWriteArrayList
是自己实现的Iterator
-
CopyOnWriteArrayList
的各个方法中不会抛出ConcurrentModificationException
异常
参考内容
- Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)