java

集合类与Iterator的坑引发

2018-12-09  本文已影响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为什么可以线程安全呢?

上一篇下一篇

猜你喜欢

热点阅读