Article directory
1. Overview of fail-fast and fail-safe
快速失败(fail-fast)
, fail-fast is an error detection mechanism for Java collections.
- Scenario : When thread A is traversing a collection object using an iterator, thread B operates on the memory of the collection object (add, delete, modify), and at this time it will throw
Concurrent Modification Exception
- Principle : For
ArrayList
example,ArrayList
an abstract class is inheritedAbstractList
, and there is a member variable in this abstractionprotected transient int modCount = 0;
, which records the number of times the collection has been modified. When a collection is traversed using an iterator, before the iterator is usedhashNext()/next()
, it will first checkmodCount
whether the variable is aexpectedmodCount
value, and if so, it will traverse; otherwise, an exception will be thrown. - Note : The judgment condition for throwing an exception here
modCount
is whether the variable isexpectedmodCount
a value. If the collection changes,modCount
the value is just set toexpectedmodCount
the value again. Therefore, you cannot program concurrent operations depending on whether this exception is thrown or not. This exception is only recommended for detecting concurrent modification bugs. - Scenario:
java.util
The collection classes under the package are all fail-fast, and cannot be modified concurrently under multi-threading (modified during iteration), such asArrayList
classes.
安全失败(fail—safe)
, using a collection container that fails safely, it does not directly access the collection content when traversing, but first copies the original collection content, and then traverses on the copied collection.
- Principle : Since the copy of the original collection is traversed during iteration, the modification made to the original collection during the traversal process cannot be detected by the iterator, so it will not be triggered
Concurrent Modification Exception
. - Disadvantages : The advantage of copying content is to avoid it
Concurrent Modification Exception
, but in the same way, the iterator cannot access the modified content, that is to say, the iterator traverses the copy of the collection obtained at the moment of traversal, and the original collection occurs during the traversal. The modified iterator is not known. - Scenario :
java.util.concurrent
The containers under the package are safe to fail, and can be used and modified concurrently under multiple threads, such asCopyOnWriteArrayList
classes.
2. Fail-fast source code analysis
Test code, use IDEA to debug
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (Integer integer : list) {
System.out.println(integer);
}
}
The enhanced for loop here actually uses iterators to iterate. The iterator records the number of modifications to the current collection.
int expectedModCount = modCount;
Before the for loop, list
four elements are added to the collection, so the value list
in modCount
is 4.
list
Use IDEA's debug tool to add an element - 5 - to the collection during program execution to simulate concurrency.
Successfully added a fifth element to the collection.
At this time, when the next enhanced loop is executed, that is, the method for
of using the iteratorhasNext
public boolean hasNext() {
return cursor != size;
}
Then execute the iterator next
method
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];
}
First, it will be called checkForComodification()
to check whether the collection has been modified. If it is modified, the ones in the collection mod
will not be expectedModCount
equal.
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
mod
If expectedModCount
it is not equal, an exception will be thrown ConcurrentModificationException
and the program will be interrupted.
3. Fail-safe source code analysis
Use CopyOnWriteArrayList
for testing.
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (Integer integer : list) {
System.out.println(integer);
}
}
Let's simply read CopyOnWriteArrayList
the source code first
//底层存放数据的数组
private transient volatile Object[] array;
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();
}
}
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
add
method, its underlying implementation is locked ReentrantLock
.
First get the length of the underlying array. Then expand. Then place the elements that need to be added to the collection at the end of the expanded array.
Then newElements
assign the new array to the underlying array array
.
Next for
break the point in the reinforcement loop.
Create an iterator for
is called when the enhanced loop is ready to begin .iterator
COWIterator
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
And the array that stores the data at the bottom layer is used array
as a parameter.
//由后续调用next返回的元素的索引。
private int cursor;
//快照,存储的是迭代器进行迭代的时候,集合的数据
private final Object[] snapshot;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
and then call hasNext
the method
public boolean hasNext() {
return cursor < snapshot.length;
}
Then, call next
the method to output the array of snapshots in sequence.
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
If at this time, thread B adds an element to the collection. But the iterator snapshot
does not change.
Because it can be fixed snapshot
at the time of initialization. Even if things change COWIterator
next . Also remains the same.array
snapshot
Therefore, the data output by the iterator is the data before the moment the iterator is created. During the iteration process, no matter what the original data of the collection becomes, the old data is output.
4. Summary
ArrayList
It isfail-fast
a classic representative that cannot be modified while traversing, otherwise an exception will be thrown.CopyOnWriteArrayList
It isfail-safe
a classic representative, which can be modified while traversing. The principle is to separate reading and writing.