日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Java ConcurrentModificationException 異常分析與解決方案

 圖書(shū)館送餐人員 2016-03-16




\


一、單線(xiàn)程

1. 異常情況舉例

只要拋出出現(xiàn)異常,可以肯定的是代碼一定有錯(cuò)誤的地方。先來(lái)看看都有哪些情況會(huì)出現(xiàn)ConcurrentModificationException異常,下面以ArrayList remove 操作進(jìn)行舉例:

使用的數(shù)據(jù)集合:
           List myList = new ArrayList();

           myList.add( '1');
           myList.add( '2');
           myList.add( '3');
           myList.add( '4');
           myList.add( '5');

以下三種情況都會(huì)出現(xiàn)異常:
           Iterator it = myList.iterator();
            while (it.hasNext()) {
                String value = it.next();
                 if (value.equals( '3')) {
                     myList.remove(value);  // error
                }
           }
      
            for (Iterator it = myList.iterator(); it.hasNext();) {
                String value = it.next();
                 if (value.equals( '3')) {
                     myList.remove(value);  // error
                }
           }
      
      
            for (String value : myList) {
                System. out.println( 'List Value:'   value);
                 if (value.equals( '3')) {
                     myList.remove(value);  // error
                }
           }   

異常信息如下:
Exception in thread 'main' java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)


2. 根本原因

以上都有3種出現(xiàn)異常的情況有一個(gè)共同的特點(diǎn),都是使用Iterator進(jìn)行遍歷,且都是通過(guò)ArrayList.remove(Object) 進(jìn)行刪除操作。
想要找出根本原因,直接查看ArrayList源碼看為什么出現(xiàn)異常:
public class ArrayList extends AbstractList
        implements Cloneable, Serializable, RandomAccess {
        
        
         @Override public boolean remove(Object object) {
        Object[] a = array;
        int s = size;
        if (object != null) {
            for (int i = 0; i < s; i  ) {
                if (object.equals(a[i])) {
                    System.arraycopy(a, i   1, a, i, --s - i);
                    a[s] = null;  // Prevent memory leak
                    size = s;
                    modCount  ;  // 只要?jiǎng)h除成功都是累加
                    return true;
                }
            }
        } else {
            for (int i = 0; i < s; i  ) {
                if (a[i] == null) {
                    System.arraycopy(a, i   1, a, i, --s - i);
                    a[s] = null;  // Prevent memory leak
                    size = s;
                    modCount  ;  // 只要?jiǎng)h除成功都是累加
                    return true;
                }
            }
        }
        return false;
    }   


    @Override public Iterator iterator() {
        return new ArrayListIterator();
    }   
        
    private class ArrayListIterator implements Iterator {
          ......
   
          // 全局修改總數(shù)保存到當(dāng)前類(lèi)中
        /** The expected modCount value */
        private int expectedModCount = modCount;

        @SuppressWarnings('unchecked') public E next() {
            ArrayList ourList = ArrayList.this;
            int rem = remaining;
               // 如果創(chuàng)建時(shí)的值不相同,拋出異常
            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (rem == 0) {
                throw new NoSuchElementException();
            }
            remaining = rem - 1;
            return (E) ourList.array[removalIndex = ourList.size - rem];
        }   
        
          ......
     }
}   

List、Set、Map 都可以通過(guò)Iterator進(jìn)行遍歷,這里僅僅是通過(guò)List舉例,在使用其他集合遍歷時(shí)進(jìn)行增刪操作都需要留意是否會(huì)觸發(fā)ConcurrentModificationException異常。


3. 解決方案

上面列舉了會(huì)出現(xiàn)問(wèn)題的幾種情況,也分析了問(wèn)題出現(xiàn)的根本原因,現(xiàn)在來(lái)總結(jié)一下怎樣才是正確的,如果避免遍歷時(shí)進(jìn)行增刪操作不會(huì)出現(xiàn)ConcurrentModificationException異常。
            // 1 使用Iterator提供的remove方法,用于刪除當(dāng)前元素
            for (Iterator it = myList.iterator(); it.hasNext();) {
                String value = it.next();
                 if (value.equals( '3')) {
                     it.remove();  // ok
                }
           }
           System. out.println( 'List Value:'   myList.toString());
      
            // 2 建一個(gè)集合,記錄需要?jiǎng)h除的元素,之后統(tǒng)一刪除             
           List templist = new ArrayList();
            for (String value : myList) {
                 if (value.equals( '3')) {
                     templist.remove(value);
                }
           }
            // 可以查看removeAll源碼,其中使用Iterator進(jìn)行遍歷
           myList.removeAll(templist);
           System. out.println( 'List Value:'   myList.toString());        
        
             // 3. 使用線(xiàn)程安全CopyOnWriteArrayList進(jìn)行刪除操作
           List myList = new CopyOnWriteArrayList();
           myList.add( '1');
           myList.add( '2');
           myList.add( '3');
           myList.add( '4');
           myList.add( '5');

           Iterator it = myList.iterator();

            while (it.hasNext()) {
                String value = it.next();
                 if (value.equals( '3')) {
                     myList.remove( '4');
                     myList.add( '6');
                     myList.add( '7');
                }
           }
           System. out.println( 'List Value:'   myList.toString());
          
            // 4. 不使用Iterator進(jìn)行遍歷,需要注意的是自己保證索引正常
            for ( int i = 0; i < myList.size(); i  ) {
                String value = myList.get(i);
                System. out.println( 'List Value:'   value);
                 if (value.equals( '3')) {
                     myList.remove(value);  // ok
                     i--; // 因?yàn)槲恢冒l(fā)生改變,所以必須修改i的位置
                }
           }
           System. out.println( 'List Value:'   myList.toString());

輸出結(jié)果都是:List Value:[1, 2, 4, 5] , 不會(huì)出現(xiàn)異常。
以上4種解決辦法在單線(xiàn)程中測(cè)試完全沒(méi)有問(wèn)題,但是如果在多線(xiàn)程中呢?


二、多線(xiàn)程

1. 同步異常情況舉例

上面針對(duì)ConcurrentModificationException異常在單線(xiàn)程情況下提出了4種解決方案,本來(lái)是可以很哈皮的洗洗睡了,但是如果涉及到多線(xiàn)程環(huán)境可能就不那么樂(lè)觀(guān)了。
下面的例子中開(kāi)啟兩個(gè)子線(xiàn)程,一個(gè)進(jìn)行遍歷,另外一個(gè)有條件刪除元素:
          final List myList = createTestData();
      
          new Thread(new Runnable() {
           
               @Override
               public void run() {
                    for (String string : myList) {
                         System.out.println('遍歷集合 value = '   string);
                     
                         try {
                              Thread.sleep(100);
                         } catch (InterruptedException e) {
                              e.printStackTrace();
                         }
                    }
               }
          }).start();
      
          new Thread(new Runnable() {
           
               @Override
               public void run() {
                
                 for (Iterator it = myList.iterator(); it.hasNext();) {
                     String value = it.next();
                 
                     System.out.println('刪除元素 value = '   value);
                 
                     if (value.equals( '3')) {
                          it.remove();
                     }
                 
                     try {
                              Thread.sleep(100);
                         } catch (InterruptedException e) {
                              e.printStackTrace();
                         }
                }
               }
          }).start();

輸出結(jié)果:
遍歷集合 value = 1
刪除元素 value = 1
遍歷集合 value = 2
刪除元素 value = 2
遍歷集合 value = 3
刪除元素 value = 3
Exception in thread 'Thread-0' 刪除元素 value = 4
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
at list.ConcurrentModificationExceptionStudy$1.run(ConcurrentModificationExceptionStudy.java:42)
at java.lang.Thread.run(Unknown Source)
刪除元素 value = 5


結(jié)論:
上面的例子在多線(xiàn)程情況下,僅使用單線(xiàn)程遍歷中進(jìn)行刪除的第1種解決方案使用it.remove(),但是測(cè)試得知4種的解決辦法中的1、2、3依然會(huì)出現(xiàn)問(wèn)題。
接著來(lái)再看一下JavaDoc對(duì)java.util.ConcurrentModificationException異常的描述:
當(dāng)方法檢測(cè)到對(duì)象的并發(fā)修改,但不允許這種修改時(shí),拋出此異常。
說(shuō)明以上辦法在同一個(gè)線(xiàn)程執(zhí)行的時(shí)候是沒(méi)問(wèn)題的,但是在異步情況下依然可能出現(xiàn)異常。


2. 嘗試方案

(1) 在所有遍歷增刪地方都加上synchronized或者使用Collections.synchronizedList,雖然能解決問(wèn)題但是并不推薦,因?yàn)樵鰟h造成的同步鎖可能會(huì)阻塞遍歷操作。
(2) 推薦使用ConcurrentHashMap或者CopyOnWriteArrayList。


3. CopyOnWriteArrayList注意事項(xiàng)

(1) CopyOnWriteArrayList不能使用Iterator.remove()進(jìn)行刪除。
(2) CopyOnWriteArrayList使用Iterator且使用List.remove(Object);會(huì)出現(xiàn)如下異常:
java.lang.UnsupportedOperationException: Unsupported operation remove
at java.util.concurrent.CopyOnWriteArrayList$ListIteratorImpl.remove(CopyOnWriteArrayList.java:804)

4. 解決方案

單線(xiàn)程情況下列出4種解決方案,但是發(fā)現(xiàn)在多線(xiàn)程情況下僅有第4種方案才能在多線(xiàn)程情況下不出現(xiàn)問(wèn)題。
           List myList = new CopyOnWriteArrayList();
           myList.add( '1');
           myList.add( '2');
           myList.add( '3');
           myList.add( '4');
           myList.add( '5');

          new Thread(new Runnable() {
            
               @Override
               public void run() {
                    for (String string : myList) {
                         System.out.println('遍歷集合 value = '   string);
                      
                         try {
                              Thread.sleep(100);
                         } catch (InterruptedException e) {
                              e.printStackTrace();
                         }
                    }
               }
          }).start();
       
          new Thread(new Runnable() {
            
               @Override
               public void run() {
                 
                    for (int i = 0; i < myList.size(); i  ) {
                         String value = myList.get(i);
                      
                         System.out.println('刪除元素 value = '   value);
                  
                     if (value.equals( '3')) {
                          myList.remove(value);
                          i--; // 注意                           
                     }
                  
                     try {
                              Thread.sleep(100);
                         } catch (InterruptedException e) {
                              e.printStackTrace();
                         }
                    }
               }
          }).start();


輸出結(jié)果:
刪除元素 value = 1
遍歷集合 value = 1
刪除元素 value = 2
遍歷集合 value = 2
刪除元素 value = 3
遍歷集合 value = 3
刪除元素 value = 4
遍歷集合 value = 4
刪除元素 value = 5
遍歷集合 value = 5

OK,搞定



三、參考資料

《How to Avoid ConcurrentModificationException when using an Iterator》

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀(guān)點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多