背景:一直以来对迭代器的问题理解不是很透彻,特别是迭代器和异常ConcurrentModificationException之间的联系。通过debug,详细了解其底层的具体实现过程。

迭代器Iterator与ConcurrentModificationException详解

 

Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。

Iterator提供了两个方法来迭代访问Collection集合里的元素,并可通过remove()来删除集合中上一次next()方法返回的集合元素。

当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove()方法删除上一次next()方法返回的集合元素才可以;否则会引发java.util.ConcurrentModificationException异常。

之所以会出现这样的异常,是因为Iterator迭代器采用的是快速失败(fast-fail)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中其它线程修改),程序立即引发ConcurrentModificationException,

而不是显示修改后结果,这样可以避免共享资源而引发的潜在问题。

 ConcurrentModificationException发生在Iterator#next()方法实现中,每次调用都会检查容器的结构是否发生变化,目的是为了避免共享资源而引发的潜在问题。

观察HashMap和ArrayList底层Iterator#next(), 可以看到fast-fail只会增加或者删除(非Iterator#remove())抛出异常;改变容器中元素的内容不存在这个问题(主要是modCount没发生变化)。

在单线程中使用迭代器,对非线程安全的容器,但是只能用Iterator#remove;否则会抛出异常。

在多线程中使用迭代器,可以使用线程安全的容器来避免异常。

使用普通的for循环遍历,效率虽然比较低下,但是不存在ConcurrentModificationException异常问题。用的也比较少。

ps:java在设计工具类时候,分别设计出线程安全和非安全的工具类,也是致力于解决这些多线程操作问题。所以无须纠结,直接使用就行。

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

在观察底层实现时,可以看到容器对象的modCount值在改变容器结构时才发生改变。

集合Collection Map改变 对迭代器遍历的影响

这里说的容器结构的改变是指 增加 或者删除元素,导致集合的大小发生改变。

观察源码发现,不论Collection或Map,对于Iterator来说:

异常是在next方法中抛出的,我们在使用迭代器的时候,一般会先进行hasNext方法的判断,再调用next方法来使用元素。

以下是对于ArrayList、 HashMap、ConcurrentHashMap三个容器的迭代器测试用例:

/**
 * Project Name:Spring0725
 * File Name:Test5.java
 * Package Name:work1201.basic
 * Date:2017年12月1日下午4:16:25
 * Copyright (c) 2017, 深圳金融电子结算中心 All Rights Reserved.
 *
*/

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.junit.Test;

/**
 * ClassName:TestIterator <br/>
 * Function: 测试Collection Map 改变集合结构对迭代器遍历有无影响
 * 在使用Iterator时候,对于Collection集合,改变集合的结构会触发ConcurrentModificationException异常;改变集合中元素的内容不会触发异常
 * 对于Map集合,线程安全map——ConcurrentHashMap改变集合Map的结构,无异常发生
 * 对于非线程安全的HashMap,使用Iterator遍历的时候,改变集合的结构,会触发ConcurrentModificationException
 * 
 * 非线程安全类的Collection或者Map,在使用Iterator过程中,有时候改变容器结构,并不会发生异常,这主要和底层的实现有关。
 * 如:List删除倒数第二个元素无异常;Map删除set集合中的最后一个元素,无异常
 * Date:     2017年12月1日 下午4:16:25 <br/>
 * @author   prd-lxw
 * @version   1.0
 * @since    JDK 1.7
 * @see      
 */
public class TestIterator {

    /**
     * 方法1
     * 迭代器更改Collection集合结构,非倒数第二个元素,会发生异常
     */
    @Test(expected = ConcurrentModificationException.class)
    public void testDeletCollection() {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            list.add(i + "");
        }

        Iterator<String> it = list.iterator();
        String ss = 19 + "";
        while (it.hasNext()) {
            if (it.next().equals(ss)) {
                System.out.println("找到元素:" + ss);
                list.remove(ss); //集合大小发生改变 ConcurrentModificationException
                //                list.add(102+""); //集合大小发生改变 ConcurrentModificationException
                //                list.set(19, 211+"");  //不会触发异常,因为没有改变Collection集合的大小
                //                it.remove();//正常
            }
        }
    }

    /**
     * 方法2
     * 删除Collection集合的倒数第二个元素,不会发生异常
     */
    @Test
    public void testDeletBack2Collection() {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            list.add(i + "");
        }

        Iterator<String> it = list.iterator();
        String ss = 18 + "";//倒数第2个元素
        while (it.hasNext()) {
            if (it.next().equals(ss)) {
                System.out.println("找到元素:" + ss);
                list.remove(ss); //集合大小发生改变 ConcurrentModificationException
                //              list.add(102+""); //集合大小发生改变 ConcurrentModificationException
                //              it.remove();//正常
            }
        }
    }

    /**
     * 方法3
     * 普通for方法遍历Collection,改变集合结构无异常
     */
    @Test
    public void testDeletCollectionFor() {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            list.add(i + "");
        }
        String ss = 15 + "";
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals(ss)) {
                list.remove(i);
            }
        }

    }

    /**
     * 方法4
     * 使用增强型的foreach遍历Collection,改变集合结构,会发生异常
     * 起底层实现和使用Iterator一致
     */
    @Test(expected = ConcurrentModificationException.class)
    public void testDeletCollectionForEach() {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            list.add(i + "");
        }
        String ss = 18 + "";
        for (String str : list) {
            if (str.equals(ss)) {
                list.remove(str);
            }
        }

    }

    /**
     * 方法5
     * 使用迭代器的过程中,改变map的结构,会触发ConcurrentModificationException异常
     * 但是如果删除的key在entrySet的结尾,比如key=10+""  就不会发生这个异常
     */
    @Test(expected = ConcurrentModificationException.class)
    public void testIteratorMapEntry() {
        HashMap<String, String> map = new HashMap<String, String>();
        for (int i = 0; i < 20; i++) {
            map.put(i + "", i + "");
        }
        Set<Entry<String, String>> entrySet = map.entrySet(); //打印entrySet集合可以发现,key=10是集合的最后一个元素
        Iterator<Entry<String, String>> it = entrySet.iterator();
        String key = 10 + "";
        while (it.hasNext()) {
            if (it.next().getKey().equals(key)) {
                System.out.println("testIteratorMapEntry找到元素:" + key);
                //改变Map
                //                map.remove(key); //ConcurrentModificationException
                map.put(21 + "", 21 + ""); //ConcurrentModificationException
                //                map.replace(key, 30 + "");//正常
                //                it.remove(); //正常
                System.out.println(map.size() + ":" + map.get(key));
            }
        }
    }

    /**
     * 方法6
     * 使用迭代器的过程中,改变map的结构,会触发ConcurrentModificationException异常
     * 但是如果删除的key在keySet的结尾,比如key=10+""  就不会发生这个异常
     * 
     * 对于非线程安全的map 使用Iterator遍历 keySet valueSet entrySet实验结果都一致
     */
    @Test(expected = ConcurrentModificationException.class)
    public void testIteratorMapKey() {
        HashMap<String, String> map = new HashMap<String, String>();
        for (int i = 0; i < 20; i++) {
            map.put(i + "", i + "");
        }

        Set<String> mapKeySet = map.keySet();//打印keySet集合可以发现,key=10是集合的最后一个元素
        Iterator<String> it = mapKeySet.iterator();
        String key = 11 + "";
        while (it.hasNext()) {
            if (it.next().equals(key)) {
                System.out.println("testIteratorMapKey找到元素:" + key);
                //改变Map
                map.remove(key); //ConcurrentModificationException
                //                map.put(21 + "", 21 + ""); //ConcurrentModificationException
                //                map.replace(key, 30 + "");//正常
                //                it.remove(); //正常
                System.out.println(map.size() + ":" + map.get(key));
            }
        }
    }

    //    ################# 线程安全类ConcurrentHashMap不存在ConcurrentModificationException问题
    /**
     * 方法7
     * 线程安全类Map entrySet操作,无异常发生
     */
    @Test
    public void testConIteratorMapEntry() {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
        for (int i = 0; i < 20; i++) {
            map.put(i + "", i + "");
        }

        Iterator<Entry<String, String>> it = map.entrySet().iterator();
        String key = 12 + "";
        while (it.hasNext()) {
            if (it.next().getKey().equals(key)) {
                System.out.println("testConIteratorMapEntry找到元素:" + key);
                //改变Map
                map.remove(key); //正常
                //                map.put(21 + "", 21 + ""); //正常
                //                map.replace(key, 30 + "");//正常
                //                it.remove(); //正常
                System.out.println(map.size() + ":" + map.get(key));
            }
        }
    }

    /**
     * 方法8
     * 无异常
     */
    @Test
    public void testConIteratorMapKey() {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
        for (int i = 0; i < 20; i++) {
            map.put(i + "", i + "");
        }

        Iterator<String> it = map.keySet().iterator();
        String key = 10 + "";
        while (it.hasNext()) {
            if (it.next().equals(key)) {

                System.out.println("testConIteratorMapKey找到元素:" + key);
                //改变Map
                //                map.remove(key); //正常
                map.put(21 + "", 21 + ""); //正常
                map.replace(key, 30 + "");//正常
                //                it.remove(); //正常
                System.out.println(map.size() + ":" + map.get(key));
            }
        }
    }

}
View Code

相关文章: