fail-fast与fail-safe
fail-fast
ArrayList的Iterator遍历是fail-fast的,直接在原来的数组上遍历,当发现原来的数组结构改变时会抛出ConcurrentModificationException异常。
ArrayList中有一个int型成员变量modCount,ArrayList的Iterator中保存了一个int型变量expectedModCount默认值为此时ArrayList的modCount。当调用ArrayList的add()、remove()和clear()方法时,会改变modCount的值。 Iterator在遍历的过程中,当执行迭代器的next()或remove()方法时会先检查expectedModCount是否等于modCount,以此作为判断结构改变的依据。
ArrayList中的add()、remove()和clear()
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
//清空所有元素
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
Iterator中的next()和remove()
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();
}
}
fail-safe
CopyOnWriteArrayList的Iterator遍历是fail-safe的,会在副本上遍历。虽然不会产生ConcurrentModification Exception异常,但不能保证数据是最新的。
当在CopyOnWriteArrayList集合上做修改时,比如调用**set()、add()和remove()**方法时,会先获取锁,保证同一时间只能有一个线程对集合做修改。然后会先将原来的数组拷贝一份,并在拷贝的数组上做修改,最后再将原来的数组替换为修改后的数组。
因为CopyOnWriteArrayList保证了多线程之间的同步修改,所以不存在线程安全问题,但是缺点也很明显:
- 需要占用额外的空间,并且写入的效率比较低
- 不能保证数据的实时性
public E set(int index, E element) {
synchronized (lock) {
Object[] es = getArray();
E oldValue = elementAt(es, index);
if (oldValue != element) {
//拷贝一份
es = es.clone();
es[index] = element;
//替换旧数组
setArray(es);
}
return oldValue;
}
}
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
public E remove(int index) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
E oldValue = elementAt(es, index);
int numMoved = len - index - 1;
Object[] newElements;
if (numMoved == 0)
newElements = Arrays.copyOf(es, len - 1);
else {
newElements = new Object[len - 1];
System.arraycopy(es, 0, newElements, 0, index);
System.arraycopy(es, index + 1, newElements, index,
numMoved);
}
setArray(newElements);
return oldValue;
}
}
总结
fail-fast不允许在遍历时改变集合的结构,因为这种方式的遍历是直接遍历原数据。
fail-safe则允许在遍历时改变集合的结构,因为这种遍历是建立在原数据副本之上的,副本相当于集合在某一时刻的“快照”。