Java - Java集合中的快速失败Fail Fast 机制
文章目錄
- 什么是 fail-fast
- 源碼解讀
- Itr
- 為什么對集合的結構進行修改會發生并發修改異常-源碼分析
- 修改方法之 remove
- 修改方法之 add
- 案例分享
- 【案例一】
- 【案例二】
- 【案例三】
- 【案例四】
- 【案例五】
- 【案例六】
- 【案例七】
- 阿里巴巴Java開發手冊中的規定
- 如何避免fail-fast拋異常
什么是 fail-fast
https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
fail-fast 機制是Java集合(Collection)中的一種錯誤機制。
在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的結構進行了修改(增加、刪除),則會拋出Concurrent Modification Exception 【并發修改異常】。
舉個例子:
在多線程環境下,線程1正在對集合進行遍歷,此時線程2對集合進行修改 , 很容易拋出Concurrent Modification Exception 。
當然了,在單線程的情況下,遍歷時對集合進行修改也會拋出Concurrent Modification Exception
此類的返回的迭代器iterator和 listIterator方法是快速失敗的:如果列表在任何時間后,迭代器創建結構修飾,以任何方式除非通過迭代器自身 remove或 add方法,迭代器都將拋出 Concurrent Modification Exception。
因此,面對并發修改,迭代器快速而干凈地失敗,而不是冒著在未來不確定的時間出現任意、非確定性行為的風險。
源碼解讀
Itr
在遍歷的時候對集合修改會發生fail-fast,遍歷集合------> 迭代器
/*** An optimized version of AbstractList.Itr*/private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint 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();}}@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 trafficcursor = i;lastRet = i - 1;checkForComodification();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}看到了吧, checkForComodification
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}在modCount != expectedModCount的時候拋出了ConcurrentModificationException,
而在next方法中上來就是調用checkForComodification,所以遍歷集合才會可能拋出并發修改異常。
那接下來就研究 modCount 和 expectedModCount 什么時候會不相等就行了唄。
- 在創建一個迭代器后,expectedModCount的初始值就是modCount了,
- 對集合修改會改變modCount,
- expectedModCount只會在迭代器的remove方法中被修改為modCount
這都是 中的內容,除了modCount 。 modCount 是ArrayList的常量,默認值 為0
為什么對集合的結構進行修改會發生并發修改異常-源碼分析
那我們說,在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的結構進行了修改(增加、刪除),則會拋出Concurrent Modification Exception 【并發修改異常】。
修改方法之 remove
modCount++ , 后面modCount會和expectedModCount不相等,進而拋出并發修改異常。
修改方法之 add
ensureCapacityInternal方法里對modCount++操作, 改變了modCount的值,所以調用
那set方法會觸發 fast fail嗎?
答案是不會。
set沒有對modCount++,所以對集合的某個元素進行修改并不會fail-fast
案例分享
【案例一】
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); Iterator<String> iter = list.iterator(); while (iter.hasNext()) {String tmp = iter.next();System.out.println(tmp);if (tmp.equals("1")) {list.remove("1");} } 1 Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:25)調用了 list# remove方法
【案例二】
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); Iterator<String> iter = list.iterator(); while (iter.hasNext()) {String tmp = iter.next();System.out.println(tmp);if (tmp.equals("3")) {list.remove("3");} } 1 2 3調用了 list# remove方法 , 居然沒有拋出并發修改異常????
remove倒數第二個元素,然而這時就沒有拋出異常了 。 再分析分析吧
cursor是下一個要返回的變量的下標
lastRet是上一個返回過的變量的下標
hasNext方法告訴我們只有在下一個變量的下標不等于size的時候會告訴我們集合還有下一個元素。
但是在remove的時候,size- -了,那么刪除“3”這個元素后,size變為3,而此時cursor也是3,那么再走到hasNext時,就發現cursor和size相等了,那么就會退出遍歷,“4”壓根就不會被遍歷到。
所以沒有拋出異常,因為remove后就退出了,還沒來得及走到next方法呢~
【案例三】
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); Iterator<String> iter = list.iterator(); while (iter.hasNext()) {String tmp = iter.next();System.out.println(tmp);if (tmp.equals("4")) {list.remove("4");} } 1 2 3 4 Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:25)接上個案例
那 刪除“4”,也就是最后一個元素,按理說刪了最后一個元素不就退出了嗎?走不到下一次的next方法呀?
其實是不對的,刪完“4”并沒有就直接退出 ! remove后size變成了3,但此時cursor是4,那么走到hasNext時,發現4!=3,就會再次進入循環,那么結果…走到了next方法,拋出了異常。。。
【案例四】
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); for (String i : list) {if ("1".equals(i)) {list.remove("1");} } Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:22)用增強for循環遍歷的,反編譯class , 和用迭代器實質是一樣的 。
【案例五】
List<String> list = Arrays.asList("1", "2", "3", "4"); for (String i : list) {if ("1".equals(i)) {list.remove("1");} } Exception in thread "main" java.lang.UnsupportedOperationExceptionat java.util.AbstractList.remove(AbstractList.java:161)at java.util.AbstractList$Itr.remove(AbstractList.java:374)at java.util.AbstractCollection.remove(AbstractCollection.java:293)at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:21)用了Array.asList()方法生成的集合,拋出的是UnsupportedOperationException,發現asList生成的ArrayList是個靜態內部類,并非java.util.ArrayList, 并沒有這些方法。
所以不能對asList生成的ArrayList進行增刪改
Java開發規范01 - 集合篇_Arrays.asList 坑
【案例六】
List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");list.add("4");Iterator<String> iter = list.iterator();while (iter.hasNext()) {String tmp = iter.next();System.out.println(tmp);if (tmp.equals("1")) {iter.remove();}} 1 2 3 4【案例七】
```java // Java code to illustrate // Fail Fast Iterator in Java import java.util.HashMap; import java.util.Iterator; import java.util.Map;public class FailFastExample {public static void main(String[] args){Map<String, String> cityCode = new HashMap<String, String>();cityCode.put("Delhi", "India");cityCode.put("Moscow", "Russia");cityCode.put("New York", "USA");Iterator iterator = cityCode.keySet().iterator();while (iterator.hasNext()) {System.out.println(cityCode.get(iterator.next()));// adding an element to Map// exception will be thrown on next call// of next() method.cityCode.put("Istanbul", "Turkey");}} } India Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)at java.util.HashMap$KeyIterator.next(HashMap.java:1466)at FailFastExample.main(FailFastExample.java:18) // Java code to demonstrate remove // case in Fail-fast iteratorsimport java.util.ArrayList; import java.util.Iterator;public class FailFastExample {public static void main(String[] args){ArrayList<Integer> al = new ArrayList<>();al.add(1);al.add(2);al.add(3);al.add(4);al.add(5);Iterator<Integer> itr = al.iterator();while (itr.hasNext()) {if (itr.next() == 2) {// will not throw Exceptionitr.remove();}}System.out.println(al);itr = al.iterator();while (itr.hasNext()) {if (itr.next() == 3) {// will throw Exception on// next call of next() methodal.remove(3);}}} } [1, 3, 4, 5] Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at FailFastExample.main(FailFastExample.java:28)阿里巴巴Java開發手冊中的規定
如何避免fail-fast拋異常
總結
以上是生活随笔為你收集整理的Java - Java集合中的快速失败Fail Fast 机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: APM - Prometheus监控系统
- 下一篇: Java - Java集合中的安全失败F