文章目錄
- 簡介
- modCount和expectedModCount
- modCount是如何被修改的
- fail-fast(快速失敗)和fail-safe(安全失敗)比較
- fail-fast解決什么問題
- 如何解決fail-fast事件
簡介
我們知道Java中Collection介面下的很多集合都是執行緒不安全的, 比如 java.util.ArrayList不是執行緒安全的, 因此如果在使用迭代器的程序中有其他執行緒修改了list,那么將拋出ConcurrentModificationException,這就是所謂fail-fast策略,
這一策略在原始碼中的實作是通過 modCount 域,modCount 顧名思義就是修改次數,對ArrayList 內容的修改都將增加這個值,那么在迭代器初始化程序中會將這個值賦給迭代器的 expectedModCount,在迭代程序中,判斷 modCount 跟 expectedModCount 是否相等,如果不相等就表示已經有其他執行緒修改了 list
注意到 modCount 宣告為 volatile,保證執行緒之間修改的可見性,
modCount和expectedModCount
modCount和expectedModCount是用于表示修改次數的,其中modCount表示集合的修改次數,這其中包括了呼叫集合本身的add, remove, clear方法等修改方法時進行的修改和呼叫集合迭代器的修改方法進行的修改,而expectedModCount則是表示迭代器對集合進行修改的次數,
設定expectedModCount的目的就是要保證在使用迭代器期間,list物件只能有這一個迭代器對list進行修改,
在創建迭代器的時候會把物件的modCount的值傳遞給迭代器的expectedModCount:
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
如果創建多個迭代器對一個集合物件進行修改的話,那么就會有一個modCount和多個expectedModCount,且modCount的值之間也會不一樣,這就導致了moCount和expectedModCount的值不一致,從而產生例外:
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
上面的代碼中的checkForComodification會檢查modCount和expectedModCount的值是否一致,不一致則拋出例外,
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount是如何被修改的
// 添加元素到佇列最后
public boolean add(E e) {
// 修改modCount
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 添加元素到指定的位置
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
// 修改modCount
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
// 添加集合
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
// 修改modCount
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
// 洗掉指定位置的元素
public E remove(int index) {
RangeCheck(index);
// 修改modCount
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
// 快速洗掉指定位置的元素
private void fastRemove(int index) {
// 修改modCount
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
// 清空集合
public void clear() {
// 修改modCount
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
- 也就是在對集合進行資料的增刪的時候都會執行modcount++, 那么如果一個執行緒還在使用迭代器遍歷這個list的時候就會發現例外, 發生 fail-fast(快速失敗)
fail-fast(快速失敗)和fail-safe(安全失敗)比較
Iterator的快速失敗是基于對底層集合做拷貝是淺拷貝,因此,它受源集合上修改的影響,java.util包下面的所有的集合類都是快速失敗的
而java.util.concurrent包下面的所有的類都是使用鎖實作安全失敗的,
快速失敗的迭代器會拋出ConcurrentModificationException例外,而安全失敗的迭代器永遠不會拋出這樣的例外,
fail-fast解決什么問題
fail-fast機制,是一種錯誤檢測機制,
它只能被用來檢測錯誤,因為JDK并不保證fail-fast機制一定會發生,只是在多執行緒環境下告訴客戶端發生了多執行緒安全問題.
所以若在多執行緒環境下使用fail-fast機制的集合,建議使用“java.util.concurrent包下的類”去取代“java.util包下的類”,
如何解決fail-fast事件
ArrayList對應的CopyOnWriteArrayList進行說明,我們先看看CopyOnWriteArrayList的原始碼:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
...
// 回傳集合對應的迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
...
private static class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot;
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
// 新建COWIterator時,將集合中的元素保存到一個新的拷貝陣列中,
// 這樣,當原始集合的資料改變,拷貝資料中的值也不會變化,
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
- CopyOnWriteArrayList是自己實作Iterator, 并且CopyOnWriteArrayList的Iterator實作類中,沒有所謂的checkForComodification(),更不會拋出ConcurrentModificationException例外
- CopyOnWriteArrayList在進行新建COWIterator時,將集合中的元素保存到一個新的拷貝陣列中,這樣,當原始集合的資料改變,拷貝資料中的值也不會變化,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/258658.html
標籤:其他
