Java容器的fail-fast fail-safe策略详细解读
fail-fast
在fail-fast中所有的集合容器都是强一致性的,因为他们在各种遍历之前,都会提取保存modCount的值,为后面每一次迭代或者遍历前进行比较,不一致则抛出并发修改异常。Collection以ArrayList为代表,Map以HashMap为代表进行验证
ArrayList
可以发现不管是forEach遍历还是iterator获取迭代器进行迭代也好,都会提前保存modCount的值,并且每次调用iterator()都会生成新的迭代器(每次都会记录当前ArrayList的modCout最新值);并且每次循环或者迭代都会判断modCount != expectedModCount,如果不一致则抛出并发修改异常ConcurrentModificationException
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount; // 记录值
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) { // 检测并发修改异常
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException(); // 检测并发修改异常
}
}
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; // 记录值
Itr() {}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
HashMap
HashMap迭代遍历主要有entrySet()、keys()、values()
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
// entrySet 迭代器
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
// entrySet foreach
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount; // 记录值
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc) // 检测并发修改异常
throw new ConcurrentModificationException();
}
}
}
final class KeySet extends AbstractSet<K> {
// 迭代器
public final Iterator<K> iterator() { return new KeyIterator(); }
// KeySet foreach
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount; // 记录值
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc) // 检测并发修改异常
throw new ConcurrentModificationException();
}
}
}
final class Values extends AbstractCollection<V> {
// 迭代器
public final Iterator<V> iterator() { return new ValueIterator(); }
// values foreach
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount; // 记录值
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc) // 检测并发修改异常
throw new ConcurrentModificationException();
}
}
}
// 迭代器
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount; // 记录值
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount) // entryset、values、keys 使用的迭代器都有并发修改异常检测
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount; // 迭代器移除节点时会更新构造方法中的记录值
}
}
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
}
总结
- fail-fast的容器是不允许在遍历或者迭代的时候修改值 ,每次指针下移的时候都会判断modCount != expectedModCount,如果不一致则抛出并发修改异常ConcurrentModificationException
- 迭代器一旦创建就会记录当前modCount的值,所以可能出现多个迭代器遍历出的结果不一样的情况
- 可以使用迭代器的remove方法删除元素,删除后会更新当前迭代器的modCount(很明显只允许当前迭代器删除数据,避免并发线程删除数据视图),ArrayList通过iterator()获取的迭代器只支持删除元素,通过listIterator()获取的迭代器还支持添加和修改元素
- map的keys、values、entrySet()都有foreach跟迭代器,都满足以上几点
- 并发修改异常ConcurrentModificationException只能用于检测并发修改时出现的bug,开发中不能够依赖这个异常是否抛出而进行并发操作的编程,如果有一个线程删除数据,有恰好有另一个线程添加了数据,那么modCout的值还是不变,并不能被某个线程迭代器所检测出来。
- fail-fast很明显在数据一致性跟可用性之间选择了数据一致性,当检测数据视图不一致的情况立马通过抛出异常中断当前迭代器指针迭代、for循环的遍历操作,保证数据的强一致性
fail-safe
在java.util.concurrent包下的容器全部都是fail-safe,它们允许在并发下修改数据,在很多网络资料中都说juc包下的容器不去检测并发修改异常,从而实现迭代时可以修改值的情况,这种说法是不够严谨的。通过CopyOnWriteArrayList、ConcurrentHashMap源码进行解析
CopyOnWriteArrayList
- CopyOnWriteArrayList的迭代器和for遍历并没有检测并发修改异常的操作,但是迭代器的set()、add()、remove()会抛出UnsupportedOperationException的异常,遍历只支持读操作,是读写分离思想的体现
- 可以使用copyOnWriteArrayList自身的增删改操作,增删改操作使用了类似String不可变类的思想,每次更新操作都会复制一份出来并替换原来的Object数组,不会影响到创建迭代器时拿到的Object数组的迭代遍历,写操作也不会阻塞读操作
- CopyOnWriteArrayList是弱一致性的,允许迭代时修改数据,在数据的一致性和可用性中选择了可用性。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// foreach中并没有出现任何检测并发修改的操作
public void forEach(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
}
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;
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code set}
* is not supported by this iterator.
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code add}
* is not supported by this iterator.
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
}
}
ConcurrentHashMap
- ConcurrentHashMap的keys()、values()、entrySet()同样也没有检测并发修改的操作
- keySet()、entrySet()支持添加删除操作,values()支持删除不支持添加操作
- ConcurrentHashMap中节点的val和next都是volatile修饰的,如果变化发生在已遍历过的部分,迭代器就不会反映出来,而如果变化发生在未遍历过的部分,迭代器就会发现并反映出来,这就是弱一致性
static final class KeyIterator<K,V> extends BaseIterator<K,V>
implements Iterator<K>, Enumeration<K> {
KeyIterator(Node<K,V>[] tab, int index, int size, int limit,
ConcurrentHashMap<K,V> map) {
super(tab, index, size, limit, map);
}
public final K next() {
Node<K,V> p;
if ((p = next) == null)
throw new NoSuchElementException();
K k = p.key;
lastReturned = p;
advance();
return k;
}
public final K nextElement() { return next(); }
}
static final class ValueIterator<K,V> extends BaseIterator<K,V>
implements Iterator<V>, Enumeration<V> {
ValueIterator(Node<K,V>[] tab, int index, int size, int limit,
ConcurrentHashMap<K,V> map) {
super(tab, index, size, limit, map);
}
public final V next() {
Node<K,V> p;
if ((p = next) == null)
throw new NoSuchElementException();
V v = p.val;
lastReturned = p;
advance();
return v;
}
public final V nextElement() { return next(); }
}
static final class EntryIterator<K,V> extends BaseIterator<K,V>
implements Iterator<Map.Entry<K,V>> {
EntryIterator(Node<K,V>[] tab, int index, int size, int limit,
ConcurrentHashMap<K,V> map) {
super(tab, index, size, limit, map);
}
public final Map.Entry<K,V> next() {
Node<K,V> p;
if ((p = next) == null)
throw new NoSuchElementException();
K k = p.key;
V v = p.val;
lastReturned = p;
advance();
return new MapEntry<K,V>(k, v, map);
}
}
总结
- JUC包下的容器都是fail-safe,并且都是在数据的一致性跟可用性中选择了可用性,允许出现数据的短期不一致,但是保证最终数据的一致性。
- 并发容器遍历的策略,有的在并发修改时的数据不能再迭代器中遍历出来,如CopyOnWriteArrayList通过读写分离,在遍历的时候保证读视图不受并发修改的影响,有的通过volatile保证数据多线程的可见性如ConcurrentHashMap。
以上便是Java容器的fail-fast fail-safe策略详细解读,仅为个人见解,如有不当欢迎在评论区交流!