java.util.ConcurrentModificationException 異常問題詳解
環(huán)境:JDK 1.8.0_111
在Java開發(fā)過程中涉茧,使用iterator遍歷集合的同時(shí)對(duì)集合進(jìn)行修改就會(huì)出現(xiàn)java.util.ConcurrentModificationException異常汰瘫,本文就以ArrayList為例去理解和解決這種異常。
一书斜、單線程情況下問題分析及解決方案
1.1 問題復(fù)現(xiàn)
先上一段拋異常的代碼诬辈。
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void test1() { 2 ArrayList<Integer> arrayList = new ArrayList<>();
3 for (int i = 0; i < 20; i++) {
4 arrayList.add(Integer.valueOf(i));
5 }
6
7 // 復(fù)現(xiàn)方法一
8 Iterator<Integer> iterator = arrayList.iterator(); 9 while (iterator.hasNext()) { 10 Integer integer = iterator.next(); 11 if (integer.intValue() == 5) { 12 arrayList.remove(integer); 13 } 14 } 15
16 // 復(fù)現(xiàn)方法二
17 iterator = arrayList.iterator(); 18 for (Integer value : arrayList) { 19 Integer integer = iterator.next(); 20 if (integer.intValue() == 5) { 21 arrayList.remove(integer); 22 } 23 } 24 }</pre>
](javascript:void(0); "復(fù)制代碼")
在這個(gè)代碼中展示了兩種能拋異常的實(shí)現(xiàn)方式。
1.2荐吉、問題原因分析
先來看實(shí)現(xiàn)方法一焙糟,方法一中使用Iterator遍歷ArrayList, 拋出異常的是iterator.next()样屠〈┐椋看下Iterator next方法實(shí)現(xiàn)源碼
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public E next() { 2 checkForComodification();
3 int i = cursor; 4 if (i >= size) 5 throw new NoSuchElementException(); 6 Object[] elementData = ArrayList.this.elementData;
7 if (i >= elementData.length) 8 throw new ConcurrentModificationException(); 9 cursor = i + 1; 10 return (E) elementData[lastRet = i]; 11 } 12
13 final void checkForComodification() { 14 if (modCount != expectedModCount) 15 throw new ConcurrentModificationException(); 16 }</pre>
](javascript:void(0); "復(fù)制代碼")
在next方法中首先調(diào)用了checkForComodification方法,該方法會(huì)判斷modCount是否等于expectedModCount痪欲,不等于就會(huì)拋出java.util.ConcurrentModificationExcepiton異常悦穿。
我們接下來跟蹤看一下modCount和expectedModCount的賦值和修改。
modCount是ArrayList的一個(gè)屬性业踢,繼承自抽象類AbstractList咧党,用于表示ArrayList對(duì)象被修改次數(shù)。
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 protected transient int modCount = 0;</pre>
整個(gè)ArrayList中修改modCount的方法比較多陨亡,有add、remove深员、clear负蠕、ensureCapacityInternal等,凡是設(shè)計(jì)到ArrayList對(duì)象修改的都會(huì)自增modCount屬性倦畅。
在創(chuàng)建Iterator的時(shí)候會(huì)將modCount賦值給expectedModCount遮糖,在遍歷ArrayList過程中,沒有其他地方可以設(shè)置expectedModCount了叠赐,因此遍歷過程中expectedModCount會(huì)一直保持初始值20(調(diào)用add方法添加了20個(gè)元素欲账,修改了20次)。
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 int expectedModCount = modCount; // 創(chuàng)建對(duì)象時(shí)初始化</pre>
遍歷的時(shí)候是不會(huì)觸發(fā)modCount自增的芭概,但是遍歷到integer.intValue() == 5的時(shí)候赛不,執(zhí)行了一次arrayList.remove(integer),這行代碼執(zhí)行后modCount++變?yōu)榱?1罢洲,但此時(shí)的expectedModCount仍然為20踢故。
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 final void checkForComodification() { 2 if (modCount != expectedModCount) 3 throw new ConcurrentModificationException(); 4 }</pre>
在執(zhí)行next方法時(shí),遇到modCount != expectedModCount方法,導(dǎo)致拋出異常java.util.ConcurrentModificationException殿较。
明白了拋出異常的過程耸峭,但是為什么要這么做呢?很明顯這么做是為了阻止程序員在不允許修改的時(shí)候修改對(duì)象淋纲,起到保護(hù)作用劳闹,避免出現(xiàn)未知異常。引用網(wǎng)上的一段解釋洽瞬,點(diǎn)擊查看解釋來源
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">Iterator 是工作在一個(gè)獨(dú)立的線程中本涕,并且擁有一個(gè) mutex 鎖。
Iterator 被創(chuàng)建之后會(huì)建立一個(gè)指向原來對(duì)象的單鏈索引表片任,當(dāng)原來的對(duì)象數(shù)量發(fā)生變化時(shí)偏友,這個(gè)索引表的內(nèi)容不會(huì)同步改變。
當(dāng)索引指針往后移動(dòng)的時(shí)候就找不到要迭代的對(duì)象对供,所以按照 fail-fast 原則 Iterator 會(huì)馬上拋出 java.util.ConcurrentModificationException 異常位他。
所以 Iterator 在工作的時(shí)候是不允許被迭代的對(duì)象被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除對(duì)象产场, Iterator.remove() 方法會(huì)在刪除當(dāng)前迭代對(duì)象的同時(shí)維護(hù)索引的一致性鹅髓。</pre>
再來分析下第二種for循環(huán)拋異常的原因:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void forEach(Consumer<? super E> action) { 2 Objects.requireNonNull(action);
3 final int expectedModCount = modCount; 4 @SuppressWarnings("unchecked")
5 final E[] elementData = (E[]) this.elementData;
6 final int size = this.size;
7 for (int i=0; modCount == expectedModCount && i < size; i++) {
8 action.accept(elementData[i]);
9 } 10 if (modCount != expectedModCount) { 11 throw new ConcurrentModificationException(); 12 } 13 }</pre>
](javascript:void(0); "復(fù)制代碼")
在for循環(huán)中一開始也是對(duì)expectedModCount采用modCount進(jìn)行賦值。在進(jìn)行for循環(huán)時(shí)每次都會(huì)有判定條件modCount == expectedModCount京景,當(dāng)執(zhí)行完arrayList.remove(integer)之后窿冯,該判定條件返回false退出循環(huán),然后執(zhí)行if語句确徙,結(jié)果同樣拋出java.util.ConcurrentModificationException異常醒串。
這兩種復(fù)現(xiàn)方法實(shí)際上都是同一個(gè)原因?qū)е碌摹?/p>
1.3 問題解決方案
上述的兩種復(fù)現(xiàn)方法都是在單線程運(yùn)行的,先來說明單線程中的解決方案:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void test2() { 2 ArrayList<Integer> arrayList = new ArrayList<>();
3 for (int i = 0; i < 20; i++) {
4 arrayList.add(Integer.valueOf(i));
5 }
6
7 Iterator<Integer> iterator = arrayList.iterator(); 8 while (iterator.hasNext()) { 9 Integer integer = iterator.next(); 10 if (integer.intValue() == 5) { 11 iterator.remove(); 12 } 13 } 14 }</pre>
](javascript:void(0); "復(fù)制代碼")
這種解決方案最核心的就是調(diào)用iterator.remove()方法鄙皇。我們看看該方法源碼為什么這個(gè)方法能避免拋出異常
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void remove() { 2 if (lastRet < 0)
3 throw new IllegalStateException(); 4 checkForComodification();
5
6 try { 7 ArrayList.this.remove(lastRet);
8 cursor = lastRet; 9 lastRet = -1; 10 expectedModCount = modCount; 11 } catch (IndexOutOfBoundsException ex) { 12 throw new ConcurrentModificationException(); 13 } 14 }</pre>
](javascript:void(0); "復(fù)制代碼")
在iterator.remove()方法中芜赌,同樣調(diào)用了ArrayList自身的remove方法,但是調(diào)用完之后并非就return了伴逸,而是expectedModCount = modCount重置了expectedModCount值缠沈,使二者的值繼續(xù)保持相等。
針對(duì)forEach循環(huán)并沒有修復(fù)方案错蝴,因此在遍歷過程中同時(shí)需要修改ArrayList對(duì)象洲愤,則需要采用iterator遍歷。
上面提出的解決方案調(diào)用的是iterator.remove()方法顷锰,如果不僅僅是想調(diào)用remove方法移除元素柬赐,還想增加元素,或者替換元素官紫,是否可以呢躺率?瀏覽Iterator源碼可以發(fā)現(xiàn)這是不行的玛界,Iterator只提供了remove方法。
但是ArrayList實(shí)現(xiàn)了ListIterator接口悼吱,ListIterator類繼承了Iter慎框,這些操作都是可以實(shí)現(xiàn)的,使用示例如下:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void test3() { 2 ArrayList<Integer> arrayList = new ArrayList<>();
3 for (int i = 0; i < 20; i++) {
4 arrayList.add(Integer.valueOf(i));
5 }
6
7 ListIterator<Integer> iterator = arrayList.listIterator(); 8 while (iterator.hasNext()) { 9 Integer integer = iterator.next(); 10 if (integer.intValue() == 5) { 11 iterator.set(Integer.valueOf(6)); 12 iterator.remove(); 13 iterator.add(integer); 14 } 15 } 16 }</pre>
](javascript:void(0); "復(fù)制代碼")
二后添、 多線程情況下的問題分析及解決方案
單線程問題解決了笨枯,再來看看多線程情況。
2.1 問題復(fù)現(xiàn)
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void test4() { 2 ArrayList<Integer> arrayList = new ArrayList<>();
3 for (int i = 0; i < 20; i++) {
4 arrayList.add(Integer.valueOf(i));
5 }
6
7 Thread thread1 = new Thread(new Runnable() { 8 @Override
9 public void run() { 10 ListIterator<Integer> iterator = arrayList.listIterator(); 11 while (iterator.hasNext()) { 12 System.out.println("thread1 " + iterator.next().intValue()); 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 } 20 }); 21
22 Thread thread2 = new Thread(new Runnable() { 23 @Override 24 public void run() { 25 ListIterator<Integer> iterator = arrayList.listIterator(); 26 while (iterator.hasNext()) { 27 System.out.println("thread2 " + iterator.next().intValue()); 28 iterator.remove(); 29 } 30 } 31 }); 32 thread1.start(); 33 thread2.start(); 34 }</pre>
](javascript:void(0); "復(fù)制代碼")
在個(gè)測(cè)試代碼中遇西,開啟兩個(gè)線程馅精,一個(gè)線程遍歷,另外一個(gè)線程遍歷加修改粱檀。程序輸出結(jié)果如下
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">thread1 0
thread2 0
thread2 1
thread2 2
thread2 3
thread2 4
thread2 5
thread2 6
thread2 7
thread2 8
thread2 9
thread2 10
thread2 11
thread2 12
thread2 13
thread2 14
thread2 15
thread2 16
thread2 17
thread2 18
thread2 19
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayListItr.next(ArrayList.java:851)
at com.snow.ExceptionTest$1.run(ExceptionTest.java:74)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0</pre>
[](javascript:void(0); "復(fù)制代碼")
2.2 問題分析
從上面代碼執(zhí)行結(jié)果可以看出thread2 遍歷結(jié)束后洲敢,thread1 sleep完1000ms準(zhǔn)備遍歷第二個(gè)元素,next的時(shí)候拋出異常了茄蚯。我們從時(shí)間點(diǎn)分析一下拋異常的原因
| 時(shí)間點(diǎn) | arrayList.modCount | thread1 iterator.expectedModCount | thread2 iterator.expectedModCount |
| thread start压彭,初始化iterator | 20 | 20 | 20 |
| thread2.remove()調(diào)用之后 | 21 | 20 | 21 |
兩個(gè)thread都是使用的同一個(gè)arrayList,thread2修改完后modCount = 21渗常,此時(shí)thread2的expectedModCount = 21 可以一直遍歷到結(jié)束壮不;thread1的expectedModCount仍然為20,因?yàn)閠hread1的expectedModCount只是在初始化的時(shí)候賦值皱碘,其后并未被修改過询一。因此當(dāng)arrayList的modCount被thread2修改為21之后,thread1想繼續(xù)遍歷必定會(huì)拋出異常了癌椿。
在這個(gè)示例代碼里面健蕊,兩個(gè)thread,每個(gè)thread都有自己的iterator踢俄,當(dāng)thread2通過iterator方法修改expectedModCount必定不會(huì)被thread1感知到绊诲。這個(gè)跟ArrayList非線程安全是無關(guān)的,即使這里面的ArrayList換成Vector也是一樣的結(jié)果褪贵,不信上測(cè)試代碼:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void test5() { 2 Vector<Integer> vector = new Vector<>();
3 for (int i = 0; i < 20; i++) {
4 vector.add(Integer.valueOf(i));
5 }
6
7 Thread thread1 = new Thread(new Runnable() { 8 @Override
9 public void run() { 10 ListIterator<Integer> iterator = vector.listIterator(); 11 while (iterator.hasNext()) { 12 System.out.println("thread1 " + iterator.next().intValue()); 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 } 20 }); 21
22 Thread thread2 = new Thread(new Runnable() { 23 @Override 24 public void run() { 25 ListIterator<Integer> iterator = vector.listIterator(); 26 while (iterator.hasNext()) { 27 Integer integer = iterator.next(); 28 System.out.println("thread2 " + integer.intValue()); 29 if (integer.intValue() == 5) { 30 iterator.remove(); 31 } 32 } 33 } 34 }); 35 thread1.start(); 36 thread2.start(); 37 }</pre>
](javascript:void(0); "復(fù)制代碼")
執(zhí)行后輸出結(jié)果為:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">thread1 0
thread2 0
thread2 1
thread2 2
thread2 3
thread2 4
thread2 5
thread2 6
thread2 7
thread2 8
thread2 9
thread2 10
thread2 11
thread2 12
thread2 13
thread2 14
thread2 15
thread2 16
thread2 17
thread2 18
thread2 19
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.VectorItr.next(Vector.java:1137)
at com.snow.ExceptionTest$3.run(ExceptionTest.java:112)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0</pre>
[](javascript:void(0); "復(fù)制代碼")
test5()方法執(zhí)行結(jié)果和test4()是相同的,那如何解決這個(gè)問題呢抗俄?
2.3 多線程下的解決方案
2.3.1 方案一:iterator遍歷過程加同步鎖脆丁,鎖住整個(gè)arrayList
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public static void test5() { 2 ArrayList<Integer> arrayList = new ArrayList<>();
3 for (int i = 0; i < 20; i++) {
4 arrayList.add(Integer.valueOf(i));
5 }
6
7 Thread thread1 = new Thread(new Runnable() { 8 @Override
9 public void run() { 10 synchronized (arrayList) { 11 ListIterator<Integer> iterator = arrayList.listIterator(); 12 while (iterator.hasNext()) { 13 System.out.println("thread1 " + iterator.next().intValue()); 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 } 21 } 22 }); 23
24 Thread thread2 = new Thread(new Runnable() { 25 @Override 26 public void run() { 27 synchronized (arrayList) { 28 ListIterator<Integer> iterator = arrayList.listIterator(); 29 while (iterator.hasNext()) { 30 Integer integer = iterator.next(); 31 System.out.println("thread2 " + integer.intValue()); 32 if (integer.intValue() == 5) { 33 iterator.remove(); 34 } 35 } 36 } 37 } 38 }); 39 thread1.start(); 40 thread2.start(); 41 }</pre>
](javascript:void(0); "復(fù)制代碼")
這種方案本質(zhì)上是將多線程通過加鎖來轉(zhuǎn)變?yōu)閱尉€程操作,確保同一時(shí)間內(nèi)只有一個(gè)線程去使用iterator遍歷arrayList动雹,其它線程等待槽卫,效率顯然是只有單線程的效率。
2.3.2 方案二:使用CopyOnWriteArrayList胰蝠,有坑歼培!要明白原理再用震蒋,否則你就呆坑里吧。
我們先來看代碼躲庄,很有意思咯
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void test6() { 2 List<Integer> list = new CopyOnWriteArrayList<>();
3 for (int i = 0; i < 20; i++) {
4 list.add(Integer.valueOf(i));
5 }
6
7 Thread thread1 = new Thread(new Runnable() { 8 @Override
9 public void run() { 10 ListIterator<Integer> iterator = list.listIterator(); 11 while (iterator.hasNext()) { 12 System.out.println("thread1 " + iterator.next().intValue()); 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 } 20 }); 21
22 Thread thread2 = new Thread(new Runnable() { 23 @Override 24 public void run() { 25 for (Integer integer : list) { 26 System.out.println("thread2 " + integer.intValue()); 27 if (integer.intValue() == 5) { 28 list.remove(integer); 29 } 30 } 31 for (Integer integer : list) { 32 System.out.println("thread2 again " + integer.intValue()); 33 } 34 // ListIterator<Integer> iterator = list.listIterator(); 35 // while (iterator.hasNext()) { 36 // Integer integer = iterator.next(); 37 // System.out.println("thread2 " + integer.intValue()); 38 // if (integer.intValue() == 5) { 39 // iterator.remove(); 40 // } 41 // }
42 } 43 }); 44 thread1.start(); 45 thread2.start(); 46 }</pre>
](javascript:void(0); "復(fù)制代碼")
先不分析查剖,看執(zhí)行結(jié)果,這個(gè)執(zhí)行結(jié)果重點(diǎn)關(guān)注字體加粗部分噪窘。
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">thread1 0
thread2 0
thread2 1
thread2 2
thread2 3
thread2 4
thread2 5
thread2 6
thread2 7
thread2 8
thread2 9
thread2 10
thread2 11
thread2 12
thread2 13
thread2 14
thread2 15
thread2 16
thread2 17
thread2 18
thread2 19
thread2 again 0
thread2 again 1
thread2 again 2
thread2 again 3
thread2 again 4
thread2 again 6
thread2 again 7
thread2 again 8
thread2 again 9
thread2 again 10
thread2 again 11
thread2 again 12
thread2 again 13
thread2 again 14
thread2 again 15
thread2 again 16
thread2 again 17
thread2 again 18
thread2 again 19
thread1 1
thread1 2
thread1 3
thread1 4
thread1 5
thread1 6
thread1 7
thread1 8
thread1 9
thread1 10
thread1 11
thread1 12
thread1 13
thread1 14
thread1 15
thread1 16
thread1 17
thread1 18
thread1 19
Process finished with exit code 0</pre>
[](javascript:void(0); "復(fù)制代碼")
我們先分析thread2的輸出結(jié)果笋庄,第一次遍歷將4 5 6都輸出,情理之中倔监;第一次遍歷后刪除掉了一個(gè)元素直砂,第二次遍歷輸出4 6,符合我們的預(yù)期浩习。
再來看下thread1的輸出結(jié)果静暂,有意思的事情來了,thread1 仍然輸出了4 5 6谱秽,什么鬼洽蛀?thread1和thread2都是遍歷list,list在thread1遍歷第二個(gè)元素的時(shí)候就已經(jīng)刪除了一個(gè)元素了弯院,為啥還能輸出5辱士?
為了了解這個(gè)問題,需要了解CopyOnWriteArrayList是如何做到一邊遍歷的同時(shí)還能一邊修改并且還不拋異常的听绳。
在這里不想再深入分析CopyOnWriteArrayList代碼颂碘,后續(xù)會(huì)專門出一篇博客來解釋這個(gè)類的源碼的。
這里說一下CopyOnWriteArrayList的解決思路椅挣,其實(shí)很簡(jiǎn)單:
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 private transient volatile Object[] array;</pre>
CopyOnWriteArrayList本質(zhì)上是對(duì)array數(shù)組的一個(gè)封裝头岔,一旦CopyOnWriteArrayList對(duì)象發(fā)生任何的修改都會(huì)new一個(gè)新的Object[]數(shù)組newElement,在newElement數(shù)組上執(zhí)行修改操作鼠证,修改完成后將newElement賦值給array數(shù)組(array=newElement)峡竣。
因?yàn)閍rray是volatile的,因此它的修改對(duì)所有線程都可見量九。
了解了CopyOnWriteArrayList的實(shí)現(xiàn)思路之后适掰,我們?cè)賮矸治錾厦娲atest6為什么會(huì)出現(xiàn)那樣的輸出結(jié)果。先來看下thread1和thread2中用到的兩種遍歷方式的源碼:
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public void forEach(Consumer<? super E> action) { 2 if (action == null) throw new NullPointerException(); 3 // 在遍歷開始前獲取當(dāng)前數(shù)組
4 Object[] elements = getArray(); 5 int len = elements.length; 6 for (int i = 0; i < len; ++i) {
7 @SuppressWarnings("unchecked") E e = (E) elements[i]; 8 action.accept(e);
9 } 10 }</pre>
](javascript:void(0); "復(fù)制代碼")
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 public ListIterator<E> listIterator() { 2 return new COWIterator<E>(getArray(), 0);
3 }
4 static final class COWIterator<E> implements ListIterator<E> { 5 /** Snapshot of the array /
6 private final Object[] snapshot; 7 /* Index of element to be returned by subsequent call to next. */
8 private int cursor; 9
10 private COWIterator(Object[] elements, int initialCursor) { 11 cursor = initialCursor; 12 // 初始化為當(dāng)前數(shù)組
13 snapshot = elements; 14 } 15
16 public void remove() { 17 // 已經(jīng)不支持Iterator remove操作了\小类浪!
18 throw new UnsupportedOperationException(); 19 } 20
21 public boolean hasNext() { 22 return cursor < snapshot.length; 23 } 24
25 @SuppressWarnings("unchecked") 26 public E next() { 27 if (! hasNext()) 28 throw new NoSuchElementException(); 29 return (E) snapshot[cursor++]; 30 } 31
32 // 此處省略其他無關(guān)代碼
33 }</pre>
](javascript:void(0); "復(fù)制代碼")
這兩種遍歷方式有個(gè)共同的特點(diǎn):都在初始化的時(shí)候?qū)?dāng)前數(shù)組保存下來了,之后的遍歷都將會(huì)遍歷這個(gè)數(shù)組肌似,而不管array如何變化费就。
| 時(shí)間點(diǎn) | CopyOnWriteArrayList的array | thread1 iterator 初始化的Object數(shù)組 | thread2 第一次遍歷forEach初始化的Object數(shù)組 | thread2 第二次遍歷forEach初始化的Object數(shù)組 |
| thread start | 假設(shè)為A | A | A | / |
| thread2 調(diào)用remove方法之后 | 假設(shè)為B | A | A | B |
有了這個(gè)時(shí)間節(jié)點(diǎn)表就很清楚了,thread1和thread2 start的時(shí)候都會(huì)將A數(shù)組初始化給自己的臨時(shí)變量川队,之后遍歷的也都是這個(gè)A數(shù)組力细,而不管CopyOnWriteArrayList中的array發(fā)生了什么變化睬澡。因此也就解釋了thread1在thread2 remove掉一個(gè)元素之后為什么還會(huì)輸出5了。在thread2中眠蚂,第二次遍歷初始化數(shù)組變成了當(dāng)前的array煞聪,也就是修改后的B,因此不會(huì)有Integer.valueOf(5)這個(gè)元素了河狐。
從test6執(zhí)行結(jié)果來看米绕,CopyOnWriteArrayList確實(shí)能解決一邊遍歷一邊修改并且還不會(huì)拋異常,但是這也是有代價(jià)的:
(1) thread2對(duì)array數(shù)組的修改thread1并不能被動(dòng)感知到馋艺,只能通過hashCode()方法去主動(dòng)感知栅干,否則就會(huì)一直使用修改前的數(shù)據(jù)
(2) 每次修改都需要重新new一個(gè)數(shù)組,并且將array數(shù)組數(shù)據(jù)拷貝到new出來的數(shù)組中捐祠,效率會(huì)大幅下降
此外CopyOnWriteArrayList中的ListIterator實(shí)現(xiàn)是不支持remove碱鳞、add和set操作的,一旦調(diào)用就會(huì)拋出UnsupportedOperationException異常踱蛀,因此test6注釋代碼34-41行中如果運(yùn)行是會(huì)拋異常的窿给。
參考文獻(xiàn):
http://lz12366.iteye.com/blog/675016
http://www.cnblogs.com/dolphin0520/p/3933551.html
http://blog.csdn.net/androiddevelop/article/details/21509345