美文网首页java
集合类与Iterator的坑引发

集合类与Iterator的坑引发

作者: 半数的年 | 来源:发表于2018-12-09 18:19 被阅读0次

    http://naotu.baidu.com/file/43a7a6effe26277d6f837295e5413afd 学习脑图
    https://www.bilibili.com/video/av35434533?from=search&seid=11201110952609046081 借鉴视频

    image.png
    题目:
        /**
         * 测试单线程下利用迭代器遍历ArrayList
         */
        public static void testSingleList(){
            ArrayList<String> list = new ArrayList<>();
            list.add("张三");
            list.add("李四");
            list.add("王五");
            list.add("小明");
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()){
                String element = iterator.next();
                if("李四".equals(element)){
                    list.remove(element);
                }
                System.out.println(element);
            }
        }
    
    image.png

    先解析下异常名字 ConcurrentModificationException:同步修改异常,字面上的理解就是在迭代器在迭代元素的时候,原来arraylist数组发生了变化,这样就导致了异常。

    上面浅显的解析了一下,下面来看下源码深入理解一下。

    image.png
    ArrayList的迭代器是一个Itr内部类,那就来看一下这个内部类
    image.png
    内部类的三个成员变量
    image.png
    image.png
    这样我们就明白了为什么会list.remove()修改了modCount,就导致了同步修改异常
    image.png
    那我们试一下将list.remove()改为iterator.remove(),发现就不报异常了,看下remove()源码
    image.png
    我们看到在用迭代器删除元素会使期望修改次数 = 实际修改次数,这样在next()中判断的时候就相等了,也不会导致异常。

    上面的是单线程的解决办法,而当使用多线程时,上面的使用迭代器的remove也不管用了。

        /**
         * 多线程下利用迭代器遍历ArrayList
         */
        public static void testConcurrentArrayList(){
            ArrayList<String> list = new ArrayList<>();
            list.add("张三");
            list.add("李四");
            list.add("王五");
            list.add("小明");
    
            Thread thread1 = new Thread(()->{
                Iterator<String> iterator = list.iterator();
                while (iterator.hasNext()){
                    String element = iterator.next();
                    if("李四".equals(element)) {
                        iterator.remove();
                    }
                }
            }) ;
    
            Thread thread2 = new Thread(()->{
                Iterator<String> iterator = list.iterator();
                while (iterator.hasNext()){
                    System.out.println(iterator.next());
                }
            }) ;
    
            thread1.start();
            thread2.start();
        }
    
    image.png

    因为在第一个线程在使用迭代器修改了list后,虽然迭代器的期望修改次数跟实际修改次数相等,而第二个线程在迭代的时候,创建的迭代器跟第一个线程迭代器是不一样的,在第一个线程修改后,第二个迭代器期望修改次数跟实际修改次数不一样了,这样在next()就会导致异常。

    修改前: modCount == 线程0--expectModCount == 线程1--expectModCount
    修改后: modCount + 1 == 线程0--expectModCount + 1 != 线程1--expectModCount
    这样子的解决办法就是使用线程安全的CopyOnWriteArrayList

        /**
         * 多线程下利用迭代器遍历CopyOnWriteArrayList
         */
        public static void testConcurrentCOWArrayList(){
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("张三");
            list.add("李四");
            list.add("王五");
            list.add("小明");
    
            Thread thread1 = new Thread(()->{
                Iterator<String> iterator = list.iterator();
                while (iterator.hasNext()){
                    String element = iterator.next();
                    if("李四".equals(element)) {
                        list.remove("李四");
    //                    iterator.remove();   // CopyOnWriteArrayList中的迭代器不支持这种操作,调用这个方法会抛出异常
                    }
                    System.out.println("0---"+element);
                }
            }) ;
    
            Thread thread2 = new Thread(()->{
                Iterator<String> iterator = list.iterator();
                while (iterator.hasNext()){
                    System.out.println("1---"+iterator.next());
                }
            }) ;
    
            thread1.start();
            thread2.start();
        }
    

    那么问题又来了,CopyOnWriteArrayList为什么可以线程安全呢?

    相关文章

      网友评论

        本文标题:集合类与Iterator的坑引发

        本文链接:https://www.haomeiwen.com/subject/flahhqtx.html