List中subList方法抛出异常java.util.ConcurrentModificationException原理分析
1、首先從測試代碼開始:
public class Test {public static void main(String[] args) {List<Integer> list = new ArrayList<>();for (int i = 0;i<6000;i++){list.add(i);}List<Integer> list1 = list.subList(0,3000);List<Integer> list2 = list.subList(3000,6000);list2.clear();System.out.println("list1 = " + list1);} }首先初始化一個6000個元素的list,然后,利用list.subList()截取3000個元素到list1中,再取出后3000個元素到list2中,然后清空list2,最后再打印list1,此時將拋出異常:
2、前戲知識:
subList()方法原理分析:
上面的測試方式為什么會出現這個情況,看上去明明沒有任何問題,但是打印list1的時候就拋出異常,肯定不可能是System.out.println()有bug吧,再來仔細看看代碼,似乎只有打印語句前面幾句話會出現問題,那么就是subList()的調用以及clear()這幾句代碼了,那么問題到底出現在哪里,我們來一探究竟;
接下來我們首先看一下ArrayList中對subList()方法的實現的源碼,看它究竟干了些什么事兒:
在subList()方法的源碼中首先調用了 subListRangeCheck(fromIndex, toIndex, size) 這個方法主要作用就是判斷subList()傳入的參數是否合規,這里不是重點,重點在于它??return new SubList(this, 0, fromIndex, toIndex),返回了一個SubList對象,繼續往下看一下這個SubList對象,源碼在1010行:
通過源碼可以看到,這個SubList對象是一個內部類,
2.1、在構造對象時,會傳入4個參數:
AbstractList<E> parent:當前調用subList()方法的list對象
int offset:偏移量(從0開始)
int fromIndex:開始下標(包含)
int toIndex:結束下標(不包含)
2.2、在構造器內部:
將傳入的parent賦給SubList對象的成員變量parent;
fromIndex賦給SubList對象的成員變量parentOffset;
offset+fromIndex賦給SubList對象的成員變量offset,用于記錄元素的偏移量;
toIndex -?fromIndex賦給SubList對象的成員變量size,用于記錄此時會返回的數據量大小;
最后一個是 ArrayList.this.modCount?賦給SubList對象的成員變量modCount ,這個賦值比較關鍵,記錄了修改過的次數,默認為0;
到這里,構造一個SubList對象就完成了,你可能會有疑問,只是單純的構造了一個SubList對象,那么是怎么進行賦值取值的;解決這個問題,來看一下SubList對象的get()方法:
在get()方法中,最終返回的是?ArrayList.this.elementData(offset + index);可以看到,它是從當前的ArrayList對象中維護的一個elementData()方法中取值,再來看elementData()這個方法:
返回的是elementData這個數組中的元素:
由此可見:SubList對象中操作的集合與原始list中操作的集合是同一個集合,通過offset偏移量加上index來標記元素的位置;所以,當你操作原始list或者截取元素后生成的list1集合,都是影響同一個集合。
3、高潮部分:
異常產生分析:
有了上面第二步的分析,有了一個基本認識,那就是list.subList()方法返回的集合會直接影響原始的list集合,接下來繼續分析java.util.ConcurrentModificationException異常出現的原因;
再次回到測試代碼的以下四句代碼:
List<Integer> list1 = list.subList(0,3000);
List<Integer> list2 = list.subList(3000,6000);
list2.clear();
System.out.println("list1 = " + list1);
首先通過??List<Integer> list1 = list.subList(0,3000); 等到一個list1;?
然后再次通過??List<Integer> list2= list.subList(3000,6000); 等到一個list2;?
然后清空list2 即list2.clear();
最后打印:System.out.println("list1 = " + list1);
由于上面分析我們知道,list2調用clear()方法,那么此時原始list維護的底層elementData數組勢必會受影響,具體就是會把這后面3000個元素給刪除掉,此時list1再去打印,它會調用自己重寫的迭代方法iterator()進行遍歷,然后調用父級AbstractList的listIterator()方法,由于SubList類繼承了AbstractList 所以它會來調用SubList類的listIterator(final int index)方法,此時該方法內部在第一句就調用了checkForComodification();這個方法:
接下來看?checkForComodification()這個方法在干什么:
重點來了,這個方法里面首先判斷了 ArrayList.this.modCount 與?this.modCount(即SubList的modCount)是否相同,如果不相同則拋出異常java.util.ConcurrentModificationException,寫得累死我了,繞了一大圈終于寫到這個異常了,在生成list1時,它在實例化一個SubList對象時將原始list的modCount賦值給了SubList對象,此時是默認值0,當list2.clear()時,原始list的modCount已經發生了變化,即不再是0,所以 此時打印list1時,checkForComodification()方法中的ArrayList.this.modCount != this.modCount判斷肯定時true,所以這就是異常拋出的原因。
4、附上一位研究了subList()方法上面的注釋得出的結論的圖供大家參考學習:
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的List中subList方法抛出异常java.util.ConcurrentModificationException原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java扑克牌(斗地主,手中牌的排序)【
- 下一篇: Java扑克牌(多线程)