天天看點

Java ConcurrentModificationException異常原因和解決方法

在前面一篇文章中提到,對vector、arraylist在疊代的時候如果同時對其進行修改就會抛出java.util.concurrentmodificationexception異常。下面我們就來讨論以下這個異常出現的原因以及解決辦法。

  以下是本文目錄大綱:

  一.concurrentmodificationexception異常出現的原因

  二.在單線程環境下的解決辦法

  三.在多線程環境下的解決方法

  若有不正之處請多多諒解,并歡迎批評指正

  請尊重作者勞動成果,轉載請标明原文連結:

  http://www.cnblogs.com/dolphin0520/p/3933551.html

  先看下面這段代碼:

1

2

3

4

5

6

7

8

9

10

11

12

<code>public</code> <code>class</code> <code>test {</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args)  {</code>

<code>        </code><code>arraylist&lt;integer&gt; list = </code><code>new</code> <code>arraylist&lt;integer&gt;();</code>

<code>        </code><code>list.add(</code><code>2</code><code>);</code>

<code>        </code><code>iterator&lt;integer&gt; iterator = list.iterator();</code>

<code>        </code><code>while</code><code>(iterator.hasnext()){</code>

<code>            </code><code>integer integer = iterator.next();</code>

<code>            </code><code>if</code><code>(integer==</code><code>2</code><code>)</code>

<code>                </code><code>list.remove(integer);</code>

<code>        </code><code>}</code>

<code>    </code><code>}</code>

<code>}</code>

   運作結果:

  

Java ConcurrentModificationException異常原因和解決方法

  從異常資訊可以發現,異常出現在checkforcomodification()方法中。

  我們不忙看checkforcomodification()方法的具體實作,我們先根據程式的代碼一步一步看arraylist源碼的實作:

  首先看arraylist的iterator()方法的具體實作,檢視源碼發現在arraylist的源碼中并沒有iterator()這個方法,那麼很顯然這個方法應該是其父類或者實作的接口中的方法,我們在其父類abstractlist中找到了iterator()方法的具體實作,下面是其實作代碼:

<code>public</code> <code>iterator&lt;e&gt; iterator() {</code>

<code>    </code><code>return</code> <code>new</code> <code>itr();</code>

   從這段代碼可以看出傳回的是一個指向itr類型對象的引用,我們接着看itr的具體實作,在abstractlist類中找到了itr類的具體實作,它是abstractlist的一個成員内部類,下面這段代碼是itr類的所有實作:

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<code>private</code> <code>class</code> <code>itr </code><code>implements</code> <code>iterator&lt;e&gt; {</code>

<code>    </code><code>int</code> <code>cursor = </code><code>0</code><code>;</code>

<code>    </code><code>int</code> <code>lastret = -</code><code>1</code><code>;</code>

<code>    </code><code>int</code> <code>expectedmodcount = modcount;</code>

<code>    </code><code>public</code> <code>boolean</code> <code>hasnext() {</code>

<code>           </code><code>return</code> <code>cursor != size();</code>

<code>    </code><code>public</code> <code>e next() {</code>

<code>           </code><code>checkforcomodification();</code>

<code>        </code><code>try</code> <code>{</code>

<code>        </code><code>e next = get(cursor);</code>

<code>        </code><code>lastret = cursor++;</code>

<code>        </code><code>return</code> <code>next;</code>

<code>        </code><code>} </code><code>catch</code> <code>(indexoutofboundsexception e) {</code>

<code>        </code><code>checkforcomodification();</code>

<code>        </code><code>throw</code> <code>new</code> <code>nosuchelementexception();</code>

<code>    </code><code>public</code> <code>void</code> <code>remove() {</code>

<code>        </code><code>if</code> <code>(lastret == -</code><code>1</code><code>)</code>

<code>        </code><code>throw</code> <code>new</code> <code>illegalstateexception();</code>

<code>        </code><code>abstractlist.</code><code>this</code><code>.remove(lastret);</code>

<code>        </code><code>if</code> <code>(lastret &lt; cursor)</code>

<code>            </code><code>cursor--;</code>

<code>        </code><code>lastret = -</code><code>1</code><code>;</code>

<code>        </code><code>expectedmodcount = modcount;</code>

<code>        </code><code>throw</code> <code>new</code> <code>concurrentmodificationexception();</code>

<code>    </code><code>final</code> <code>void</code> <code>checkforcomodification() {</code>

<code>        </code><code>if</code> <code>(modcount != expectedmodcount)</code>

   首先我們看一下它的幾個成員變量:

  cursor:表示下一個要通路的元素的索引,從next()方法的具體實作就可看出

  lastret:表示上一個通路的元素的索引

  expectedmodcount:表示對arraylist修改次數的期望值,它的初始值為modcount。

  modcount是abstractlist類中的一個成員變量

<code>protected</code> <code>transient</code> <code>int</code> <code>modcount = </code><code>0</code><code>;</code>

   該值表示對list的修改次數,檢視arraylist的add()和remove()方法就可以發現,每次調用add()方法或者remove()方法就會對modcount進行加1操作。

  好了,到這裡我們再看看上面的程式:

  當調用list.iterator()傳回一個iterator之後,通過iterator的hashnext()方法判斷是否還有元素未被通路,我們看一下hasnext()方法,hashnext()方法的實作很簡單:

<code>public</code> <code>boolean</code> <code>hasnext() {</code>

<code>    </code><code>return</code> <code>cursor != size();</code>

   如果下一個通路的元素下标不等于arraylist的大小,就表示有元素需要通路,這個很容易了解,如果下一個通路元素的下标等于arraylist的大小,則肯定到達末尾了。

  然後通過iterator的next()方法擷取到下标為0的元素,我們看一下next()方法的具體實作:

<code>public</code> <code>e next() {</code>

<code>    </code><code>checkforcomodification();</code>

<code> </code><code>try</code> <code>{</code>

<code>    </code><code>e next = get(cursor);</code>

<code>    </code><code>lastret = cursor++;</code>

<code>    </code><code>return</code> <code>next;</code>

<code> </code><code>} </code><code>catch</code> <code>(indexoutofboundsexception e) {</code>

<code>    </code><code>throw</code> <code>new</code> <code>nosuchelementexception();</code>

<code> </code><code>}</code>

   這裡是非常關鍵的地方:首先在next()方法中會調用checkforcomodification()方法,然後根據cursor的值擷取到元素,接着将cursor的值賦給lastret,并對cursor的值進行加1操作。初始時,cursor為0,lastret為-1,那麼調用一次之後,cursor的值為1,lastret的值為0。注意此時,modcount為0,expectedmodcount也為0。

  接着往下看,程式中判斷目前元素的值是否為2,若為2,則調用list.remove()方法來删除該元素。

  我們看一下在arraylist中的remove()方法做了什麼:

<code>public</code> <code>boolean</code> <code>remove(object o) {</code>

<code>    </code><code>if</code> <code>(o == </code><code>null</code><code>) {</code>

<code>        </code><code>for</code> <code>(</code><code>int</code> <code>index = </code><code>0</code><code>; index &lt; size; index++)</code>

<code>            </code><code>if</code> <code>(elementdata[index] == </code><code>null</code><code>) {</code>

<code>                </code><code>fastremove(index);</code>

<code>                </code><code>return</code> <code>true</code><code>;</code>

<code>            </code><code>}</code>

<code>    </code><code>} </code><code>else</code> <code>{</code>

<code>            </code><code>if</code> <code>(o.equals(elementdata[index])) {</code>

<code>    </code><code>return</code> <code>false</code><code>;</code>

<code>private</code> <code>void</code> <code>fastremove(</code><code>int</code> <code>index) {</code>

<code>    </code><code>modcount++;</code>

<code>    </code><code>int</code> <code>nummoved = size - index - </code><code>1</code><code>;</code>

<code>    </code><code>if</code> <code>(nummoved &gt; </code><code>0</code><code>)</code>

<code>        </code><code>system.arraycopy(elementdata, index+</code><code>1</code><code>, elementdata, index,</code>

<code>                </code><code>nummoved);</code>

<code>    </code><code>elementdata[--size] = </code><code>null</code><code>; </code><code>// let gc do its work</code>

   通過remove方法删除元素最終是調用的fastremove()方法,在fastremove()方法中,首先對modcount進行加1操作(因為對集合修改了一次),然後接下來就是删除元素的操作,最後将size進行減1操作,并将引用置為null以友善垃圾收集器進行回收工作。

  那麼注意此時各個變量的值:對于iterator,其expectedmodcount為0,cursor的值為1,lastret的值為0。

  對于list,其modcount為1,size為0。

  接着看程式代碼,執行完删除操作後,繼續while循環,調用hasnext方法()判斷,由于此時cursor為1,而size為0,那麼傳回true,是以繼續執行while循環,然後繼續調用iterator的next()方法:

  注意,此時要注意next()方法中的第一句:checkforcomodification()。

  在checkforcomodification方法中進行的操作是:

<code>final</code> <code>void</code> <code>checkforcomodification() {</code>

<code>    </code><code>if</code> <code>(modcount != expectedmodcount)</code>

<code>    </code><code>throw</code> <code>new</code> <code>concurrentmodificationexception();</code>

   如果modcount不等于expectedmodcount,則抛出concurrentmodificationexception異常。

  很顯然,此時modcount為1,而expectedmodcount為0,是以程式就抛出了concurrentmodificationexception異常。

  到這裡,想必大家應該明白為何上述代碼會抛出concurrentmodificationexception異常了。

  關鍵點就在于:調用list.remove()方法導緻modcount和expectedmodcount的值不一緻。

  注意,像使用for-each進行疊代實際上也會出現這種問題。

  既然知道原因了,那麼如何解決呢?

  其實很簡單,細心的朋友可能發現在itr類中也給出了一個remove()方法:

<code>public</code> <code>void</code> <code>remove() {</code>

<code>    </code><code>if</code> <code>(lastret == -</code><code>1</code><code>)</code>

<code>    </code><code>throw</code> <code>new</code> <code>illegalstateexception();</code>

<code>       </code><code>checkforcomodification();</code>

<code>    </code><code>try</code> <code>{</code>

<code>    </code><code>abstractlist.</code><code>this</code><code>.remove(lastret);</code>

<code>    </code><code>if</code> <code>(lastret &lt; cursor)</code>

<code>        </code><code>cursor--;</code>

<code>    </code><code>lastret = -</code><code>1</code><code>;</code>

<code>    </code><code>expectedmodcount = modcount;</code>

<code>    </code><code>} </code><code>catch</code> <code>(indexoutofboundsexception e) {</code>

   在這個方法中,删除元素實際上調用的就是list.remove()方法,但是它多了一個操作:

<code>expectedmodcount = modcount;</code>

   是以,在疊代器中如果要删除元素的話,需要調用itr類的remove方法。

  将上述代碼改為下面這樣就不會報錯了:

<code>                </code><code>iterator.remove();   </code><code>//注意這個地方</code>

  上面的解決辦法在單線程環境下适用,但是在多線程下适用嗎?看下面一個例子:

<code>    </code><code>static</code> <code>arraylist&lt;integer&gt; list = </code><code>new</code> <code>arraylist&lt;integer&gt;();</code>

<code>        </code><code>list.add(</code><code>1</code><code>);</code>

<code>        </code><code>list.add(</code><code>3</code><code>);</code>

<code>        </code><code>list.add(</code><code>4</code><code>);</code>

<code>        </code><code>list.add(</code><code>5</code><code>);</code>

<code>        </code><code>thread thread1 = </code><code>new</code> <code>thread(){</code>

<code>            </code><code>public</code> <code>void</code> <code>run() {</code>

<code>                </code><code>iterator&lt;integer&gt; iterator = list.iterator();</code>

<code>                </code><code>while</code><code>(iterator.hasnext()){</code>

<code>                    </code><code>integer integer = iterator.next();</code>

<code>                    </code><code>system.out.println(integer);</code>

<code>                    </code><code>try</code> <code>{</code>

<code>                        </code><code>thread.sleep(</code><code>100</code><code>);</code>

<code>                    </code><code>} </code><code>catch</code> <code>(interruptedexception e) {</code>

<code>                        </code><code>e.printstacktrace();</code>

<code>                    </code><code>}</code>

<code>                </code><code>}</code>

<code>            </code><code>};</code>

<code>        </code><code>};</code>

<code>        </code><code>thread thread2 = </code><code>new</code> <code>thread(){</code>

<code>                    </code><code>if</code><code>(integer==</code><code>2</code><code>)</code>

<code>                        </code><code>iterator.remove(); </code>

<code>        </code><code>thread1.start();</code>

<code>        </code><code>thread2.start();</code>

Java ConcurrentModificationException異常原因和解決方法

  有可能有朋友說arraylist是非線程安全的容器,換成vector就沒問題了,實際上換成vector還是會出現這種錯誤。

  原因在于,雖然vector的方法采用了synchronized進行了同步,但是由于vector是繼承的abstarctlist,是以通過iterator來通路容器的話,事實上是不需要擷取鎖就可以通路。那麼顯然,由于使用iterator對容器進行通路不需要擷取鎖,在多線程中就會造成當一個線程删除了元素,由于modcount是abstarctlist的成員變量,是以可能會導緻在其他線程中modcount和expectedmodcount值不等。

  就比如上面的代碼中,很顯然iterator是線程私有的,

  初始時,線程1和線程2中的modcount、expectedmodcount都為0,

  當線程2通過iterator.remove()删除元素時,會修改modcount值為1,并且會修改線程2中的expectedmodcount的值為1,

  而此時線程1中的expectedmodcount值為0,雖然modcount不是volatile變量,不保證線程1一定看得到線程2修改後的modcount的值,但是也有可能看得到線程2對modcount的修改,這樣就有可能導緻線程1中比較expectedmodcount和modcount不等,而抛出異常。

  是以一般有2種解決辦法:

  1)在使用iterator疊代的時候使用synchronized或者lock進行同步;

  2)使用并發容器copyonwritearraylist代替arraylist和vector。

  關于并發容器的内容将在下一篇文章中講述。

  參考資料:

 文末加上原文連結:[http://wely.iteye.com/blog/2324814]